diff --git a/.example.env b/.example.env index 3e71d08fae3..04055c21c5e 100644 --- a/.example.env +++ b/.example.env @@ -47,9 +47,6 @@ REACT_SENTRY_DSN= # Sentry environment (default: staging) REACT_SENTRY_ENVIRONMENT= -# Sample format file paths -REACT_SAMPLE_FORMAT_ASSET_IMPORT=/asset-import-template.xlsx - # Camera feed, still watching idle timeout (in seconds; default: 180) REACT_STILL_WATCHING_IDLE_TIMEOUT= diff --git a/.github/workflows/label-merge-conflict.yml b/.github/workflows/label-merge-conflict.yml index 5c3293e48ce..fd92df00d25 100644 --- a/.github/workflows/label-merge-conflict.yml +++ b/.github/workflows/label-merge-conflict.yml @@ -24,6 +24,6 @@ jobs: wait_ms: 15000 detect_merge_changes: false conflict_comment: | - Conflicts have been detected against the base branch. Please rebase your branch against the base branch. + Conflicts have been detected against the base branch. Please merge the base branch into your branch. cc: @${author} > _See: https://docs.ohc.network/docs/contributing#how-to-resolve-merge-conflicts_ diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 7b2ff575aa8..698aec731e1 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -28,3 +28,6 @@ jobs: - name: Run lint run: npm run lint -- --quiet + + - name: Run unimported + run: npm run unimported diff --git a/.unimportedrc.json b/.unimportedrc.json new file mode 100644 index 00000000000..fcea15622f2 --- /dev/null +++ b/.unimportedrc.json @@ -0,0 +1,54 @@ +{ + "ignorePatterns": [ + "node_modules/**", + "**/*.d.ts" + ], + "ignoreUnimported": [ + "src/Locale/update_locale.js", + "src/PluginRegistry.ts", + "src/pluginTypes.ts", + "src/service-worker.ts" + ], + "ignoreUnused": [ + "@fontsource/figtree", + "@originjs/vite-plugin-federation", + "@vitejs/plugin-react", + "browserslist-useragent-regexp", + "cross-env", + "postcss-loader" + ], + "ignoreUnresolved": [ + [ + "./pluginMap", + [ + "src/pluginTypes.ts" + ] + ], + [ + "@/supportedBrowsers", + [ + "src/components/ErrorPages/BrowserWarning.tsx" + ] + ], + [ + "tsx/cjs", + [ + "node_modules/find-cypress-specs/src/index.js" + ] + ], + [ + "virtual:pwa-register", + [ + "src/index.tsx" + ] + ] + ], + "respectGitignore": true, + "entry": [ + "src/index.tsx", + "vite.config.mts", + "tailwind.config.js", + "cypress.config.ts", + "scripts/**" + ] +} \ No newline at end of file diff --git a/care.config.ts b/care.config.ts index 0bd759fd246..6813a60b331 100644 --- a/care.config.ts +++ b/care.config.ts @@ -64,11 +64,6 @@ const careConfig = { reCaptchaSiteKey: env.REACT_RECAPTCHA_SITE_KEY || "6LdvxuQUAAAAADDWVflgBqyHGfq-xmvNJaToM0pN", - sampleFormats: { - assetImport: - env.REACT_SAMPLE_FORMAT_ASSET_IMPORT || "/asset-import-template.xlsx", - }, - wartimeShifting: boolean("REACT_WARTIME_SHIFTING"), stillWatching: { diff --git a/cypress/e2e/patient_spec/patient_creation.cy.ts b/cypress/e2e/patient_spec/patient_creation.cy.ts index 9cec5afc0fd..8f2c1606c38 100644 --- a/cypress/e2e/patient_spec/patient_creation.cy.ts +++ b/cypress/e2e/patient_spec/patient_creation.cy.ts @@ -8,7 +8,6 @@ import { PatientFormData, patientCreation, } from "@/pageObject/Patients/PatientCreation"; -import { patientDashboard } from "@/pageObject/Patients/PatientDashboard"; import { PatientEncounter } from "@/pageObject/Patients/PatientEncounter"; import { patientVerify } from "@/pageObject/Patients/PatientVerify"; import { FacilityCreation } from "@/pageObject/facility/FacilityCreation"; @@ -126,7 +125,7 @@ describe("Patient Management", () => { .clickSubmitEncounter() .assertEncounterCreationSuccess(); - patientDashboard.verifyEncounterPatientInfo([ + patientEncounter.verifyEncounterPatientInfo([ ENCOUNTER_TYPE, ENCOUNTER_STATUS, ENCOUNTER_PRIORITY, @@ -154,7 +153,7 @@ describe("Patient Management", () => { .clickSubmitEncounter() .assertEncounterCreationSuccess(); - patientDashboard.verifyEncounterPatientInfo([ + patientEncounter.verifyEncounterPatientInfo([ ENCOUNTER_TYPE, ENCOUNTER_STATUS, ENCOUNTER_PRIORITY, diff --git a/cypress/e2e/patient_spec/patient_details.cy.ts b/cypress/e2e/patient_spec/patient_details.cy.ts new file mode 100644 index 00000000000..0f8d9c897a9 --- /dev/null +++ b/cypress/e2e/patient_spec/patient_details.cy.ts @@ -0,0 +1,35 @@ +import { PatientDetails } from "@/pageObject/Patients/PatientDetails"; +import { PatientEncounter } from "@/pageObject/Patients/PatientEncounter"; +import { FacilityCreation } from "@/pageObject/facility/FacilityCreation"; + +const facilityCreation = new FacilityCreation(); +const patientEncounter = new PatientEncounter(); +const patientDetails = new PatientDetails(); + +describe("Patient Management", () => { + beforeEach(() => { + cy.loginByApi("doctor"); + cy.visit("/"); + }); + + it("Assign users to a patient", () => { + const userName = "nihal-nurse"; + const userRole = "Nurse"; + facilityCreation.selectFacility("GHC Trikaripur"); + patientEncounter + .navigateToEncounters() + .openFirstEncounterDetails() + .clickPatientDetailsButton(); + patientDetails + .clickUsersTab() + .clickAssignUserButton() + .selectUserToAssign(userName) + .selectUserRole(userRole) + .confirmUserAssignment() + .verifyUserAssignmentSuccess() + .verifyUserContent([userName]) + .clickRemoveUserButton() + .confirmUserRemoval() + .verifyUserRemovalSuccess(); + }); +}); diff --git a/cypress/pageObject/Patients/PatientDashboard.ts b/cypress/pageObject/Patients/PatientDashboard.ts deleted file mode 100644 index 9d608b17ea6..00000000000 --- a/cypress/pageObject/Patients/PatientDashboard.ts +++ /dev/null @@ -1,8 +0,0 @@ -class PatientDashboard { - verifyEncounterPatientInfo(contents: string[]) { - cy.verifyContentPresence("#patient-infobadges", contents); - return this; - } -} - -export const patientDashboard = new PatientDashboard(); diff --git a/cypress/pageObject/Patients/PatientDetails.ts b/cypress/pageObject/Patients/PatientDetails.ts new file mode 100644 index 00000000000..1a5c2979fac --- /dev/null +++ b/cypress/pageObject/Patients/PatientDetails.ts @@ -0,0 +1,61 @@ +export class PatientDetails { + clickUsersTab() { + cy.verifyAndClickElement('[data-cy="tab-users"]', "Users"); + return this; + } + + clickAssignUserButton() { + cy.verifyAndClickElement('[data-cy="assign-user-button"]', "Assign User"); + return this; + } + + selectUserToAssign(username: string) { + cy.typeAndSelectOption( + '[data-cy="patient-user-selector-container"]', + username, + false, + ); + return this; + } + + selectUserRole(role: string) { + cy.clickAndSelectOption('[data-cy="patient-user-role-select"]', role); + return this; + } + + confirmUserAssignment() { + cy.verifyAndClickElement( + '[data-cy="patient-user-assign-button"]', + "Assign to Patient", + ); + return this; + } + + verifyUserAssignmentSuccess() { + cy.verifyNotification("User added to patient successfully"); + return this; + } + + clickRemoveUserButton() { + cy.get('[data-cy="patient-user-remove-button"]').first().click(); + return this; + } + + confirmUserRemoval() { + cy.verifyAndClickElement( + '[data-cy="patient-user-remove-confirm-button"]', + "Remove", + ); + return this; + } + + verifyUserContent(expectedTexts: string[]) { + cy.verifyContentPresence('[data-cy="patient-users"]', expectedTexts); + return this; + } + + verifyUserRemovalSuccess() { + cy.verifyNotification("User removed successfully"); + return this; + } +} diff --git a/cypress/pageObject/Patients/PatientEncounter.ts b/cypress/pageObject/Patients/PatientEncounter.ts index fd8dd325572..95e3b8c1fe0 100644 --- a/cypress/pageObject/Patients/PatientEncounter.ts +++ b/cypress/pageObject/Patients/PatientEncounter.ts @@ -22,6 +22,11 @@ export class PatientEncounter { return this; } + verifyEncounterPatientInfo(contents: string[]) { + cy.verifyContentPresence("#patient-infobadges", contents); + return this; + } + // Questionnaire actions addQuestionnaire(questionnaireName: string) { cy.get('[data-cy="add-questionnaire-button"]').click(); diff --git a/package-lock.json b/package-lock.json index 70ae01d59a5..f6c19f9cbe5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,6 @@ "license": "MIT", "dependencies": { "@fontsource/figtree": "^5.1.1", - "@googlemaps/react-wrapper": "^1.1.42", - "@googlemaps/typescript-guards": "^2.0.3", "@headlessui/react": "^2.2.0", "@hookform/resolvers": "^3.10.0", "@originjs/vite-plugin-federation": "^1.3.7", @@ -41,7 +39,6 @@ "@tanstack/react-query": "^5.64.1", "@tanstack/react-query-devtools": "^5.64.2", "@vitejs/plugin-react": "^4.3.4", - "@yudiel/react-qr-scanner": "^2.1.0", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", "browserslist": "^4.24.4", @@ -53,10 +50,6 @@ "cypress": "^14.0.0", "date-fns": "^3.6.0", "dayjs": "^1.11.13", - "echarts": "^5.5.1", - "echarts-for-react": "^3.0.2", - "events": "^3.3.0", - "hi-profiles": "^1.1.0", "html-to-image": "^1.11.11", "i18next": "^24.2.1", "i18next-browser-languagedetector": "^8.0.2", @@ -64,7 +57,6 @@ "i18next-resources-to-backend": "^1.2.1", "input-otp": "^1.4.2", "libphonenumber-js": "^1.11.18", - "lodash-es": "^4.17.21", "lucide-react": "^0.474.0", "markdown-it": "^14.1.0", "next-themes": "^0.4.3", @@ -77,7 +69,6 @@ "react-google-recaptcha": "^3.1.0", "react-hook-form": "^7.53.2", "react-i18next": "^15.2.0", - "react-infinite-scroll-component": "^6.1.0", "react-intersection-observer": "^9.15.1", "react-pdf": "^9.2.1", "react-phone-number-input": "^3.4.11", @@ -87,8 +78,6 @@ "tailwind-merge": "^3.0.0", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", - "vaul": "^1.1.1", - "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "zod": "^3.23.8" }, "devDependencies": { @@ -140,6 +129,7 @@ "ts-node": "^10.9.2", "tsx": "^4.19.2", "typescript": "^5.6.3", + "unimported": "^1.31.0", "uuid": "^11.0.2", "vite": "^5.4.10", "vite-plugin-checker": "^0.8.0", @@ -4031,30 +4021,6 @@ "integrity": "sha512-WlauXrKAtHOadYti0l/dA2ez4lK7Yw2iQccv3ulmkMXgfKOdMjngob2AkIVxFyNLcvorVhh/Eq77DiWSGoR2bw==", "license": "OFL-1.1" }, - "node_modules/@googlemaps/js-api-loader": { - "version": "1.16.8", - "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz", - "integrity": "sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==", - "license": "Apache-2.0" - }, - "node_modules/@googlemaps/react-wrapper": { - "version": "1.1.42", - "resolved": "https://registry.npmjs.org/@googlemaps/react-wrapper/-/react-wrapper-1.1.42.tgz", - "integrity": "sha512-rZBqreFTpHmNgB4vYBFkWPjXrjg3HDRmCSQFugCJ7wT45B+Ueh3pzjSvvHHGGIqtSJ3nS5VXRqBhRgpanP0saw==", - "license": "Apache-2.0", - "dependencies": { - "@googlemaps/js-api-loader": "^1.13.2" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@googlemaps/typescript-guards": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@googlemaps/typescript-guards/-/typescript-guards-2.0.3.tgz", - "integrity": "sha512-3iHuO8H0jPehftsMK0kgyJzPYU/g/oiTRw+wu/yltqSZ7wJPt3vfsJHkPiuRpQjbnnWygX+T3mkRGyK/eyZ/lw==", - "license": "Apache-2.0" - }, "node_modules/@headlessui/react": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.0.tgz", @@ -4121,6 +4087,49 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "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", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -4135,6 +4144,15 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/@humanwhocodes/retry": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", @@ -4302,6 +4320,23 @@ } } }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true, + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -6503,12 +6538,6 @@ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, - "node_modules/@types/dom-webcodecs": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.11.tgz", - "integrity": "sha512-yPEZ3z7EohrmOxbk/QTAa0yonMFkNkjnVXqbGb7D4rMr+F1dGQ8ZUFxXkyLLJuiICPejZ0AZE9Rrk9wUCczx4A==", - "license": "MIT" - }, "node_modules/@types/dompurify": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.2.0.tgz", @@ -6520,12 +6549,6 @@ "dompurify": "*" } }, - "node_modules/@types/emscripten": { - "version": "1.39.13", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz", - "integrity": "sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==", - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -6600,6 +6623,13 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -7248,6 +7278,14 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", @@ -7352,20 +7390,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@yudiel/react-qr-scanner": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@yudiel/react-qr-scanner/-/react-qr-scanner-2.1.0.tgz", - "integrity": "sha512-O3832Qk8YU+vnLO+tsJalfQcXRZ1pOB9l6WrI3OdwpxkQEzukpT48M6Hc2vUUe1rFE6qapQnV3RGRFNM0S7CHw==", - "license": "MIT", - "dependencies": { - "barcode-detector": "^2.3.1", - "webrtc-adapter": "9.0.1" - }, - "peerDependencies": { - "react": "^17 || ^18 || ^19", - "react-dom": "^17 || ^18 || ^19" - } - }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -7901,16 +7925,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/barcode-detector": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-2.3.1.tgz", - "integrity": "sha512-D9KEtrquS1tmBZduxBZl8qublIKnRrFqD8TAHDYcLCyrHQBo+vitIxmjMJ61LvXjXyAMalOlO7q0Oh/9Rl2PbQ==", - "license": "MIT", - "dependencies": { - "@types/dom-webcodecs": "0.1.11", - "zxing-wasm": "1.3.4" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -7956,8 +7970,8 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -8376,6 +8390,19 @@ "node": ">=8" } }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-table3": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", @@ -8437,12 +8464,74 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.8" } @@ -9135,8 +9224,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "clone": "^1.0.2" }, @@ -9521,36 +9610,6 @@ "safer-buffer": "^2.1.0" } }, - "node_modules/echarts": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", - "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "2.3.0", - "zrender": "5.6.1" - } - }, - "node_modules/echarts-for-react": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.2.tgz", - "integrity": "sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "size-sensor": "^1.0.1" - }, - "peerDependencies": { - "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0", - "react": "^15.0.0 || >=16.0.0" - } - }, - "node_modules/echarts/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -10365,15 +10424,6 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/execa": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", @@ -10458,6 +10508,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -10791,6 +10842,48 @@ "dev": true, "license": "ISC" }, + "node_modules/flow-parser": { + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.156.0.tgz", + "integrity": "sha512-OCE3oIixhOttaV4ahIGtxf9XfaDdxujiTnXuHu+0dvDVVDiSDJlQpgCWdDKqP0OHfFnxQKrjMamArDAXtrBtZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/flow-remove-types": { + "version": "2.156.0", + "resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.156.0.tgz", + "integrity": "sha512-ivU28S1ycaVo5anxlXdIVlrmqLD81JWFADa6oUjyoQg0va4zSyZCp5OYco74VZi7nm42O/yTNv1Y5Byc7bJF1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "flow-parser": "^0.156.0", + "pirates": "^3.0.2", + "vlq": "^0.2.1" + }, + "bin": { + "flow-node": "flow-node", + "flow-remove-types": "flow-remove-types" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flow-remove-types/node_modules/pirates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-3.0.2.tgz", + "integrity": "sha512-c5CgUJq6H2k6MJz72Ak1F5sN9n9wlSlJyEnwvpm9/y3WB4E3pHBDT2c6PEiS1vyJvq2bUxUAIu0EGf8Cx4Ic7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-modules-regexp": "^1.0.0" + }, + "engines": { + "node": ">= 4" + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -10972,6 +11065,16 @@ "node": ">=18" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "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", @@ -11364,16 +11467,6 @@ "node": ">= 0.4" } }, - "node_modules/hi-profiles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hi-profiles/-/hi-profiles-1.1.0.tgz", - "integrity": "sha512-xlxkjsjT9dfsent7RnRbBgFpDx++jtErU7Iupnp/GgcDyEFlFbWNiEaxEGepKlZ91Y1XjbhtBzzKRg+7LGQEfg==", - "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-icons": "^4.11.0" - } - }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -11383,6 +11476,13 @@ "react-is": "^16.7.0" } }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -12003,6 +12103,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -13207,12 +13317,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -13941,6 +14045,16 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha512-JMaRS9L4wSRIR+6PTVEikTrq/lMGEZR43a48ETeilY0Q0iMwVnccMFrUM1k+tNzmYuIU0Vh710bCUqHX+/+ctQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -13960,6 +14074,50 @@ "node": ">=18" } }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -14171,9 +14329,33 @@ "node": ">= 0.8.0" } }, - "node_modules/ospath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", "license": "MIT" }, @@ -14242,6 +14424,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -15264,27 +15456,6 @@ } } }, - "node_modules/react-icons": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", - "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", - "license": "MIT", - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-infinite-scroll-component": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz", - "integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==", - "license": "MIT", - "dependencies": { - "throttle-debounce": "^2.1.0" - }, - "peerDependencies": { - "react": ">=16.0.0" - } - }, "node_modules/react-intersection-observer": { "version": "9.15.1", "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.15.1.tgz", @@ -15480,12 +15651,122 @@ "pify": "^2.3.0" } }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -15763,6 +16044,16 @@ "dev": true, "license": "MIT" }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -15887,6 +16178,69 @@ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "license": "MIT" }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -16137,12 +16491,6 @@ "loose-envify": "^1.1.0" } }, - "node_modules/sdp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz", - "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==", - "license": "MIT" - }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -16428,11 +16776,21 @@ "simple-concat": "^1.0.0" } }, - "node_modules/size-sensor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.2.tgz", - "integrity": "sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==", - "license": "ISC" + "node_modules/simple-git": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", + "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } }, "node_modules/slash": { "version": "3.0.0", @@ -16557,6 +16915,42 @@ "dev": true, "license": "MIT" }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/spec-change": { "version": "1.11.11", "resolved": "https://registry.npmjs.org/spec-change/-/spec-change-1.11.11.tgz", @@ -16635,8 +17029,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -17255,6 +17649,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/terminal-size": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.0.tgz", @@ -17294,6 +17701,14 @@ "devOptional": true, "license": "MIT" }, + "node_modules/text-table": { + "version": "0.2.0", + "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 + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -17315,15 +17730,6 @@ "node": ">=0.8" } }, - "node_modules/throttle-debounce": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz", - "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/throttleit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", @@ -17842,29 +18248,535 @@ "node": ">=4" } }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "node_modules/unimported": { + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/unimported/-/unimported-1.31.0.tgz", + "integrity": "sha512-QcbdHplhQzZfis6gs+IrbuSJ3hR7pN+w78p0WzvYwxU6SVjiwGZNOAlXbo6WFghA1MWmHPchi0L/2EfHnb549A==", "dev": true, "license": "MIT", "dependencies": { - "crypto-random-string": "^2.0.0" + "@typescript-eslint/parser": "^6.7.3", + "@typescript-eslint/typescript-estree": "^6.7.3", + "chalk": "^4.1.0", + "cosmiconfig": "^8.3.6", + "debug": "^4.3.2", + "file-entry-cache": "^6.0.1", + "flow-remove-types": "2.156.0", + "glob": "^7.1.6", + "ignore": "^5.2.4", + "json5": "^2.2.0", + "ora": "^5.3.0", + "read-pkg-up": "^7.0.1", + "resolve": "^1.20.0", + "simple-git": "^3.18.0", + "term-size": "^2.2.1", + "typescript": "^5.2.2", + "yargs": "^16.2.0" + }, + "bin": { + "unimported": "bin/unimported.js" }, "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/unimported/node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/untildify": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/unimported/node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/unimported/node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/unimported/node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "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" + } + }, + "node_modules/unimported/node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/unimported/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/unimported/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/unimported/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/unimported/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/unimported/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/unimported/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/unimported/node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "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", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/unimported/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/unimported/node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/unimported/node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/unimported/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/unimported/node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/unimported/node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/unimported/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/unimported/node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/unimported/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/unimported/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unimported/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/unimported/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unimported/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", @@ -18019,6 +18931,17 @@ "devOptional": true, "license": "MIT" }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/validator": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", @@ -18029,19 +18952,6 @@ "node": ">= 0.10" } }, - "node_modules/vaul": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", - "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-dialog": "^1.1.1" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" - } - }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -18742,6 +19652,13 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/vlq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", + "dev": true, + "license": "MIT" + }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", @@ -18871,8 +19788,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "defaults": "^1.0.3" } @@ -18887,19 +19804,6 @@ "node": ">=12" } }, - "node_modules/webrtc-adapter": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.1.tgz", - "integrity": "sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==", - "license": "BSD-3-Clause", - "dependencies": { - "sdp": "^3.2.0" - }, - "engines": { - "node": ">=6.0.0", - "npm": ">=3.10.0" - } - }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -19644,18 +20548,6 @@ } } }, - "node_modules/xlsx": { - "version": "0.20.3", - "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", - "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==", - "license": "Apache-2.0", - "bin": { - "xlsx": "bin/xlsx.njs" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -19673,6 +20565,16 @@ "dev": true, "license": "MIT" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -19691,6 +20593,67 @@ "node": ">= 14" } }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -19733,30 +20696,6 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/zrender": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", - "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", - "license": "BSD-3-Clause", - "dependencies": { - "tslib": "2.3.0" - } - }, - "node_modules/zrender/node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, - "node_modules/zxing-wasm": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-1.3.4.tgz", - "integrity": "sha512-9l0QymyATF19FmI92QHe7Dayb+BUN7P7zFAt5iDgTnUf0dFWokz6GVA/W9EepjW5q8s3e89fIE/7uxpX27yqEQ==", - "license": "MIT", - "dependencies": { - "@types/emscripten": "^1.39.13" - } } } } diff --git a/package.json b/package.json index ed274a57c55..8d2e5a37f1e 100644 --- a/package.json +++ b/package.json @@ -43,14 +43,13 @@ "cypress:install": "cross-env NODE_ENV=development cypress install", "prepare": "husky", "lint": "eslint ./src", + "unimported": "unimported", "lint-fix": "eslint ./src --fix", "format": "prettier ./src ./cypress --write", "sort-locales": "node ./scripts/sort-locales.js" }, "dependencies": { "@fontsource/figtree": "^5.1.1", - "@googlemaps/react-wrapper": "^1.1.42", - "@googlemaps/typescript-guards": "^2.0.3", "@headlessui/react": "^2.2.0", "@hookform/resolvers": "^3.10.0", "@originjs/vite-plugin-federation": "^1.3.7", @@ -79,7 +78,6 @@ "@tanstack/react-query": "^5.64.1", "@tanstack/react-query-devtools": "^5.64.2", "@vitejs/plugin-react": "^4.3.4", - "@yudiel/react-qr-scanner": "^2.1.0", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", "browserslist": "^4.24.4", @@ -91,10 +89,6 @@ "cypress": "^14.0.0", "date-fns": "^3.6.0", "dayjs": "^1.11.13", - "echarts": "^5.5.1", - "echarts-for-react": "^3.0.2", - "events": "^3.3.0", - "hi-profiles": "^1.1.0", "html-to-image": "^1.11.11", "i18next": "^24.2.1", "i18next-browser-languagedetector": "^8.0.2", @@ -102,7 +96,6 @@ "i18next-resources-to-backend": "^1.2.1", "input-otp": "^1.4.2", "libphonenumber-js": "^1.11.18", - "lodash-es": "^4.17.21", "lucide-react": "^0.474.0", "markdown-it": "^14.1.0", "next-themes": "^0.4.3", @@ -115,7 +108,6 @@ "react-google-recaptcha": "^3.1.0", "react-hook-form": "^7.53.2", "react-i18next": "^15.2.0", - "react-infinite-scroll-component": "^6.1.0", "react-intersection-observer": "^9.15.1", "react-pdf": "^9.2.1", "react-phone-number-input": "^3.4.11", @@ -125,8 +117,6 @@ "tailwind-merge": "^3.0.0", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", - "vaul": "^1.1.1", - "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "zod": "^3.23.8" }, "devDependencies": { @@ -178,6 +168,7 @@ "ts-node": "^10.9.2", "tsx": "^4.19.2", "typescript": "^5.6.3", + "unimported": "^1.31.0", "uuid": "^11.0.2", "vite": "^5.4.10", "vite-plugin-checker": "^0.8.0", @@ -207,6 +198,7 @@ "*.{ts,tsx,js,jsx}": [ "prettier --write --ignore-unknown --plugin prettier-plugin-tailwindcss --plugin @trivago/prettier-plugin-sort-imports", "eslint --fix", + "unimported", "git update-index --again" ], "public/locale/*.json": [ @@ -217,4 +209,4 @@ "node": ">=22.8.0" }, "packageManager": "npm@10.9.2" -} +} \ No newline at end of file diff --git a/public/asset-import-template.xlsx b/public/asset-import-template.xlsx deleted file mode 100644 index 9d56afe3c3c..00000000000 Binary files a/public/asset-import-template.xlsx and /dev/null differ diff --git a/public/locale/en.json b/public/locale/en.json index 79525b6771b..fb0e5e3381d 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -242,7 +242,7 @@ "SYSTEM__org_type__team": "Team", "Submit": "Submit", "TELEMEDICINE": "Telemedicine", - "TRANSPORTATION TO BE ARRANGED": "Transportation", + "TRANSPORTATION_TO_BE_ARRANGED": "Transportation", "URINATION_FREQUENCY__DECREASED": "Decreased", "URINATION_FREQUENCY__INCREASED": "Increased", "URINATION_FREQUENCY__NORMAL": "Normal", @@ -265,7 +265,7 @@ "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", + "View_Facility": "View Facility", "aadhaar_number": "Aadhaar Number", "aadhaar_number_will_not_be_stored": "Aadhaar number will not be stored by CARE", "aadhaar_otp_send_error": "Failed to send OTP. Please try again later.", @@ -319,6 +319,7 @@ "add_spoke": "Add Spoke Facility", "add_tags": "Add Tags", "add_user": "Add User", + "added_on": "Added on", "additional_information": "Additional Information", "additional_instructions": "Additional Instructions", "address": "Address", @@ -485,6 +486,7 @@ "cancel_appointment": "Cancel Appointment", "cancel_appointment_warning": "This action cannot be undone. The appointment will be cancelled and the patient will be notified.", "cancelled": "Cancelled", + "cannot_go_before_prescription_date": "Cannot view slots before the earliest prescription date", "cannot_select_date_out_of_range": "Cannot select date out of range", "cannot_select_month_out_of_range": "Cannot select month out of range", "cannot_select_year_out_of_range": "Cannot select year out of range", @@ -752,12 +754,14 @@ "downloading": "Downloading", "downloading_abha_card": "Generating ABHA Card, Please hold on", "downloads": "Downloads", + "draft": "Draft", "drag_drop_image_to_upload": "Drag & drop image to upload", "duplicate_patient_record_birth_unknown": "Please contact your district care coordinator, the shifting facility or the patient themselves if you are not sure about the patient's year of birth.", "duplicate_patient_record_confirmation": "Admit the patient record to your facility by adding the year of birth", "duplicate_patient_record_rejection": "I confirm that the suspect / patient I want to create is not on the list.", "duration": "Duration", "edit": "Edit", + "edit_administration": "Edit Administration", "edit_avatar": "Edit Avatar", "edit_avatar_note": "Change the avatar of the user", "edit_avatar_note_self": "Change your avatar", @@ -902,6 +906,9 @@ "end_datetime": "End Date/Time", "end_dose": "End Dose", "end_time": "End Time", + "end_time_before_start_error": "End time cannot be before start time", + "end_time_future_error": "End time cannot be in the future", + "ended": "Ended", "enter_dosage_instructions": "Enter Dosage Instructions", "enter_file_name": "Enter File Name", "enter_message": "Start typing...", @@ -1074,6 +1081,7 @@ "icmr_specimen_referral_form": "ICMR Specimen Referral Form", "immunisation-records": "Immunisation", "in_consultation": "In-Consultation", + "in_progress": "In Progress", "inactive": "Inactive", "incoming": "Incoming", "incomplete_patient_details_warning": "Patient details are incomplete. Please update the details before proceeding.", @@ -1140,6 +1148,7 @@ "is_it_upshift": "is it upshift", "is_phone_a_whatsapp_number": "Is the phone number a WhatsApp number?", "is_pregnant": "Is pregnant", + "is_this_administration_for_a_past_time": "Is this administration for a past time", "is_this_an_emergency": "Is this an Emergency?", "is_this_an_emergency_request": "Is this an emergency request?", "is_this_an_upshift": "Is this an upshift?", @@ -1255,8 +1264,10 @@ "medical_records": "Medical Records", "medical_worker": "Medical Worker", "medication": "Medication", + "medication_administration_saved": "Medicine Administration saved", "medication_taken_between": "Medication Taken Between", "medicine": "Medicine", + "medicine_administration": "Medicine Administration", "medicine_administration_history": "Medicine Administration History", "medicine_prescription": "Medicine Prescription", "medicines_administered": "Medicine(s) administered", @@ -1314,6 +1325,7 @@ "next_sessions": "Next Sessions", "next_week_short": "Next wk", "no": "No", + "no_active_medications": "No active medications", "no_address_provided": "No address provided", "no_allergies_recorded": "No allergies recorded", "no_appointments": "No appointments found", @@ -1350,6 +1362,7 @@ "no_log_updates": "No log updates found", "no_medical_history_available": "No Medical History Available", "no_medications_found_for_this_encounter": "No medications found for this encounter.", + "no_medications_to_administer": "No medications to administer", "no_notices_for_you": "No notices for you.", "no_observations": "No Observations", "no_ongoing_medications": "No Ongoing Medications", @@ -1398,6 +1411,7 @@ "none": "None", "normal": "Normal", "noshow": "No-show", + "not_done": "Not Done", "not_eligible": "Not Eligible", "not_found": "Not Found", "not_specified": "Not Specified", @@ -1548,6 +1562,7 @@ "phone_number_not_found": "Phone number not found", "phone_number_validation_error": "Entered phone number is not valid", "phone_number_verified": "Phone Number Verified", + "pick_a_date": "Pick a date", "pincode": "Pincode", "pincode_autofill": "State and District auto-filled from Pincode", "pincode_district_auto_fill_error": "Failed to auto-fill district information", @@ -1601,6 +1616,7 @@ "practitioner_information": "Practitioner Information", "preferred_facility_type": "Preferred Facility Type", "preferred_vehicle": "Preferred Vehicle", + "prescribed": "Prescribed", "prescription": "Prescription", "prescription_details": "Prescription Details", "prescription_discontinued": "Prescription discontinued", @@ -1836,6 +1852,7 @@ "search_investigation_placeholder": "Search Investigation & Groups", "search_medication": "Search Medication", "search_medications": "Search for medications to add", + "search_medicine": "Search Medicine", "search_patient_page_text": "Search for existing patients using their phone number or create a new patient record", "search_patients": "Search Patients", "search_resource": "Search Resource", @@ -1935,6 +1952,7 @@ "shifting_details": "Shifting details", "shifting_history": "Shifting History", "shifting_status": "Shifting status", + "show": "Show", "show_abha_profile": "Show ABHA Profile", "show_all": "Show all", "show_all_notifications": "Show All", @@ -1976,6 +1994,8 @@ "start_new_clinical_encounter": "Start a new clinical encounter", "start_review": "Start Review", "start_time": "Start Time", + "start_time_before_authored_error": "Start time cannot be before the medication was prescribed", + "start_time_future_error": "Start time cannot be in the future", "start_time_must_be_before_end_time": "Start time must be before end time", "state": "State", "state_reason_for_archiving": "State reason for archiving {{name}} file?", @@ -2010,6 +2030,7 @@ "tachycardia": "Tachycardia", "tag_name": "Tag Name", "tag_slug": "Tag Slug", + "taken": "Taken", "taper_titrate_dosage": "Taper & Titrate Dosage", "target_dosage": "Target Dosage", "template_deleted": "Template has been deleted", diff --git a/src/CAREUI/display/SubHeading.tsx b/src/CAREUI/display/SubHeading.tsx deleted file mode 100644 index 67072fd9e02..00000000000 --- a/src/CAREUI/display/SubHeading.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { ReactNode } from "react"; - -import RecordMeta from "@/CAREUI/display/RecordMeta"; -import CareIcon from "@/CAREUI/icons/CareIcon"; - -interface Props { - title: ReactNode; - lastModified?: string; - className?: string; - options?: ReactNode; -} - -export default function SubHeading(props: Props) { - return ( -
-
- - {props.title} - - {props.lastModified && ( -
- - -
- )} -
- {props.options && ( -
- {props.options} -
- )} -
- ); -} diff --git a/src/CAREUI/misc/PaginatedList.tsx b/src/CAREUI/misc/PaginatedList.tsx deleted file mode 100644 index 01b9b0538b5..00000000000 --- a/src/CAREUI/misc/PaginatedList.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import { createContext, useContext, useEffect, useState } from "react"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { Button, ButtonProps } from "@/components/ui/button"; - -import Pagination from "@/components/Common/Pagination"; - -import { ApiRoute, PaginatedResponse } from "@/Utils/request/types"; -import useTanStackQueryInstead, { - QueryOptions, -} from "@/Utils/request/useQuery"; -import { classNames } from "@/Utils/utils"; - -const DEFAULT_PER_PAGE_LIMIT = 14; - -interface PaginatedListContext - extends ReturnType>> { - items: TItem[]; - perPage: number; - currentPage: number; - setPage: (page: number) => void; -} - -const context = createContext | null>(null); - -function useContextualized() { - const ctx = useContext(context); - - if (ctx === null) { - throw new Error("Component must be used within a PaginatedList"); - } - - return ctx as PaginatedListContext; -} - -interface Props extends QueryOptions> { - route: ApiRoute>; - perPage?: number; - initialPage?: number; - onPageChange?: (page: number) => void; - queryCB?: ( - query: ReturnType>>, - ) => void; - children: ( - ctx: PaginatedListContext, - query: ReturnType>>, - ) => JSX.Element | JSX.Element[]; -} - -export default function PaginatedList({ - children, - route, - perPage = DEFAULT_PER_PAGE_LIMIT, - queryCB, - ...queryOptions -}: Props) { - const [currentPage, _setPage] = useState(queryOptions.initialPage ?? 1); - - const setPage = (page: number) => { - _setPage(page); - queryOptions.onPageChange?.(page); - }; - - const query = useTanStackQueryInstead(route, { - ...queryOptions, - query: { - ...queryOptions.query, - limit: perPage, - offset: (currentPage - 1) * perPage, - }, - }); - - const items = query.data?.results ?? []; - - useEffect(() => { - if (queryCB) { - queryCB(query); - } - }, [query]); - - return ( - - - {(ctx) => children(ctx as PaginatedListContext, query)} - - - ); -} - -interface WhenEmptyProps { - className?: string; - children: JSX.Element | JSX.Element[]; -} - -const WhenEmpty = (props: WhenEmptyProps) => { - const { items, loading } = useContextualized(); - - if (loading || items.length > 0) { - return null; - } - - return
{props.children}
; -}; - -PaginatedList.WhenEmpty = WhenEmpty; - -const WhenLoading = (props: WhenEmptyProps) => { - const { loading } = useContextualized(); - - if (!loading) { - return null; - } - - return
{props.children}
; -}; - -PaginatedList.WhenLoading = WhenLoading; - -interface CommonButtonProps extends ButtonProps { - label?: string; -} - -const Refresh = ({ label = "Refresh", ...props }: CommonButtonProps) => { - const { loading, refetch } = useContextualized(); - - return ( - - ); -}; - -PaginatedList.Refresh = Refresh; - -interface ItemsProps { - className?: string; - children: (item: TItem, items: TItem[]) => JSX.Element | JSX.Element[]; - shimmer?: JSX.Element; - shimmerCount?: number; -} - -const Items = (props: ItemsProps) => { - const { loading, items } = useContextualized(); - - if (loading || items.length === 0) { - return null; - } - - return ( -
    - {loading && props.shimmer - ? Array.from({ length: props.shimmerCount ?? 8 }).map((_, i) => ( -
  • - {props.shimmer} -
  • - )) - : items.map((item, index, items) => ( -
  • - {props.children(item, items)} -
  • - ))} -
- ); -}; - -PaginatedList.Items = Items; - -interface PaginatorProps { - className?: string; - hideIfSinglePage?: boolean; -} - -const Paginator = ({ - className, - hideIfSinglePage, -}: PaginatorProps) => { - const { data, perPage, currentPage, setPage } = useContextualized(); - const { loading } = useContextualized(); - - if (loading) { - return null; - } - - if (hideIfSinglePage && (data?.count ?? 0) <= perPage) { - return null; - } - - return ( - - ); -}; - -PaginatedList.Paginator = Paginator; diff --git a/src/Providers/AuthUserProvider.tsx b/src/Providers/AuthUserProvider.tsx index 7b94ba73e4a..a0e9aefeac2 100644 --- a/src/Providers/AuthUserProvider.tsx +++ b/src/Providers/AuthUserProvider.tsx @@ -9,7 +9,8 @@ import { AuthUserContext } from "@/hooks/useAuthUser"; import { LocalStorageKeys } from "@/common/constants"; -import routes from "@/Utils/request/api"; +import routes, { Type } from "@/Utils/request/api"; +import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; import request from "@/Utils/request/request"; import { TokenData } from "@/types/auth/otpToken"; @@ -85,6 +86,20 @@ export default function AuthUserProvider({ }; const signOut = useCallback(async () => { + const accessToken = localStorage.getItem(LocalStorageKeys.accessToken); + const refreshToken = localStorage.getItem(LocalStorageKeys.refreshToken); + + if (accessToken && refreshToken) { + try { + await mutate({ + ...routes.logout, + TRes: Type>(), + })({ access: accessToken, refresh: refreshToken }); + } catch (error) { + console.error("Error during logout:", error); + } + } + localStorage.removeItem(LocalStorageKeys.accessToken); localStorage.removeItem(LocalStorageKeys.refreshToken); localStorage.removeItem(LocalStorageKeys.patientTokenKey); diff --git a/src/Routers/routes/questionnaireRoutes.tsx b/src/Routers/routes/questionnaireRoutes.tsx index ccc89f17f90..670861016a7 100644 --- a/src/Routers/routes/questionnaireRoutes.tsx +++ b/src/Routers/routes/questionnaireRoutes.tsx @@ -6,6 +6,7 @@ import { AppRoutes } from "@/Routers/AppRouter"; const QuestionnaireRoutes: AppRoutes = { "/questionnaire": () => , + "/questionnaire/create": () => , "/questionnaire/:id": ({ id }) => , "/questionnaire/:id/edit": ({ id }) => , }; diff --git a/src/Utils/request/api.tsx b/src/Utils/request/api.tsx index 45eddeedb24..9066aab0e1f 100644 --- a/src/Utils/request/api.tsx +++ b/src/Utils/request/api.tsx @@ -17,6 +17,7 @@ import { import { PaginatedResponse } from "@/Utils/request/types"; import { AppointmentPatientRegister } from "@/pages/Patient/Utils"; import { Encounter, EncounterEditRequest } from "@/types/emr/encounter"; +import { MedicationAdministration } from "@/types/emr/medicationAdministration/medicationAdministration"; import { MedicationStatement } from "@/types/emr/medicationStatement"; import { PartialPatientModel, Patient } from "@/types/emr/newPatient"; import { @@ -102,6 +103,12 @@ const routes = { TBody: Type(), }, + logout: { + path: "/api/v1/auth/logout/", + method: "POST", + TBody: Type(), + }, + token_refresh: { path: "/api/v1/auth/token/refresh/", method: "POST", @@ -649,6 +656,14 @@ const routes = { TRes: Type>(), }, }, + + medicationAdministration: { + list: { + path: "/api/v1/patient/{patientId}/medication/administration/", + method: "GET", + TRes: Type>(), + }, + }, } as const; export default routes; diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index e83973cfeeb..8d07981307d 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -6,7 +6,8 @@ import * as Notifications from "@/Utils/Notifications"; import { HTTPError, StructuredError } from "@/Utils/request/types"; export function handleHttpError(error: Error) { - if (error.name === "AbortError") { + // Skip handling silent errors and AbortError + if (("silent" in error && error.silent) || error.name === "AbortError") { return; } @@ -15,10 +16,6 @@ export function handleHttpError(error: Error) { return; } - if (error.silent) { - return; - } - const cause = error.cause; if (isNotFound(error)) { diff --git a/src/common/schemaParser.ts b/src/common/schemaParser.ts deleted file mode 100644 index d4c2d89e1ee..00000000000 --- a/src/common/schemaParser.ts +++ /dev/null @@ -1,197 +0,0 @@ -export interface SingleKeySchema { - parent?: string; - prop: string; - type: string; - oneOf?: string[]; - required?: boolean; - parse?: (value: any) => any; -} - -export interface SchemaType { - [key: string]: SingleKeySchema; -} - -export interface DataWithError { - [key: string]: { - value: any; - error?: string; - }; -} - -export interface ParsedData { - [key: string]: any; -} - -export interface ErrorData { - index: number; - key: string; - error: string; -} - -interface parseDataProps { - dataWithErrors: DataWithError[]; - parsedData: ParsedData[]; - errors: ErrorData[]; -} - -const validateAndParse = ( - key: string, - value: any, - schema: SingleKeySchema, -): { [key: string]: { value: any; error?: string } } => { - try { - const parsedValue = schema?.parse?.(value) ?? value; - const expectedType = schema?.type; - - if ( - (parsedValue === undefined || parsedValue === null) && - !schema?.required - ) { - return { [key]: { value: parsedValue } }; - } - - if (typeof parsedValue !== expectedType && expectedType !== "any") { - return { - [key]: { - value: parsedValue, - error: `${key} should be of type ${expectedType}`, - }, - }; - } - - if (schema?.oneOf && !schema?.oneOf.includes(parsedValue)) { - return { - [key]: { - value: parsedValue, - error: `${key} should be one of the ${schema?.oneOf}`, - }, - }; - } - - if ( - schema?.required && - (parsedValue === undefined || parsedValue === null) - ) { - return { - [key]: { value: parsedValue, error: `${key} is required` }, - }; - } - - return { [key]: { value: parsedValue } }; - } catch (error: any) { - return { [key]: { value, error: error.message } }; - } -}; - -const parseDataWithSchema = ( - data: any[], - schema: SchemaType, -): parseDataProps => { - const errors: ErrorData[] = []; - const parsedData: ParsedData[] = []; - const dataWithErrors: DataWithError[] = data.map((item, index) => { - return Object.keys(schema).reduce((acc, key) => { - const { - [key]: { value, error }, - } = validateAndParse(key, item[key], schema[key]); - const parsedRow = { [schema[key].prop]: value }; - if (error) { - errors.push({ index, key, error }); - } - const prop = schema[key].prop || key; - - if (schema[key].parent) { - const indexKey = schema[key].parent || key; - acc[indexKey] = acc[indexKey] || {}; - acc[indexKey][prop] = { value, error }; - - if (!parsedData[index]) { - parsedData[index] = {}; - } - - parsedData[index][indexKey] = { - ...(parsedData[index][indexKey] || {}), - [prop]: value, - }; - } else { - acc[prop] = { value, error }; - - if (!parsedData[index]) { - parsedData[index] = {}; - } - - parsedData[index] = { ...parsedData[index], ...parsedRow }; - } - - return acc; - }, {} as ParsedData); - }); - - return { dataWithErrors, parsedData, errors }; -}; -/** - * This function takes in an array of JSON data and a schema and returns the parsed data and the data with errors - * @param dataArray The array of JSON data to be parsed - * @param schema The schema to validate and parse the data against - * @returns An object containing the parsed data, data with errors, and data without errors - * @example - * const data = [ - * { name: "Ram", age: 25 }, - * { name: "Raj", age: "30" }, - * { name: "Sam", age: 35 }, - * ]; - * - * const schema = { - * name: { prop: "name", type: "string", required: true }, - * age: { prop: "age", type: "number", required: true , parse: (value) => { - * if(value < 0 || value > 100) throw new Error("age should be between 0 and 100"); - * return value; - * }, - * }; - * - * const { dataWithErrors, parsedData, ParsedDataWithOutErrors, errors } = schemaParser(data, schema); - * - * dataWithErrors => [ - * { name: { value: "Ram" }, age: { value: 25 } }, - * { name: { value: "Raj" }, age: { value: "30", error: "age should be of type number" } }, - * { name: { value: "Sam" }, age: { value: 35 } }, - * ] - * - * parsedData => [ - * { name: "Ram", age: 25 }, - * { name: "Raj", age: "30" }, - * { name: "Sam", age: 35 }, - * ] - * - * ParsedDataWithOutErrors => [ - * { name: "Ram", age: 25 }, - * { name: "Sam", age: 35 }, - * ] - * - * errors => [ - * { index: 1, key: "age", error: "age should be of type number" } - * ] - * - */ -const schemaParser = ( - dataArray: any[], - schema: SchemaType, -): parseDataProps & { ParsedDataWithOutErrors: ParsedData[] } => { - const { dataWithErrors, parsedData, errors } = parseDataWithSchema( - dataArray, - schema, - ); - - const ParsedDataWithOutErrors = parsedData.filter((item, index) => { - return !Object.values(dataWithErrors[index]).some((item) => item.error); - }); - - return { - dataWithErrors, - parsedData, - ParsedDataWithOutErrors, - errors, - }; -}; - -export default schemaParser; diff --git a/src/common/utils.tsx b/src/common/utils.tsx deleted file mode 100644 index a2f4a681037..00000000000 --- a/src/common/utils.tsx +++ /dev/null @@ -1,15 +0,0 @@ -export const deepEqual = (x: any, y: any): boolean => { - if (x === y) return true; - - if (typeof x == "object" && x != null && typeof y == "object" && y != null) { - if (Object.keys(x).length != Object.keys(y).length) return false; - - Object.keys(x).forEach((key) => { - if (!deepEqual(x[key], y[key])) return false; - }); - - return true; - } - - return false; -}; diff --git a/src/components/Common/AvatarEditable.tsx b/src/components/Common/AvatarEditable.tsx deleted file mode 100644 index bbf3ab6c35f..00000000000 --- a/src/components/Common/AvatarEditable.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { cn } from "@/lib/utils"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { Avatar, AvatarProps } from "@/components/Common/Avatar"; - -interface AvatarEditableProps extends AvatarProps { - id?: string; - editable?: boolean; - onClick?: () => void; -} - -const AvatarEditable: React.FC = ({ - id, - colors: propColors, - name, - imageUrl, - className, - editable = true, - onClick, -}) => { - const { t } = useTranslation(); - return ( -
- - - {editable && ( -
- - {t(imageUrl ? "edit" : "upload")} -
- )} -
- ); -}; - -export default AvatarEditable; diff --git a/src/components/Common/ExcelFIleDragAndDrop.tsx b/src/components/Common/ExcelFIleDragAndDrop.tsx deleted file mode 100644 index 1689d5b3d21..00000000000 --- a/src/components/Common/ExcelFIleDragAndDrop.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { toast } from "sonner"; -import * as XLSX from "xlsx"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { Button } from "@/components/ui/button"; - -import ExcelViewer from "@/components/Common/ExcelViewer"; - -import useDragAndDrop from "@/hooks/useDragAndDrop"; - -import schemaParser, { - ErrorData, - ParsedData, - SchemaType, -} from "@/common/schemaParser"; - -interface Props { - handleSubmit: (data: any) => void; - loading: boolean; - sampleLink?: string; - schema: SchemaType; - onClose?: () => void; - setIsValid?: (value: boolean) => void; -} - -export default function ExcelFileDragAndDrop({ - handleSubmit, - loading = false, - sampleLink, - schema, - onClose, - setIsValid, -}: Props) { - const [fileData, setFileData] = useState([]); - const [errors, setErrors] = useState([]); - const [preview, setPreview] = useState(false); - const [selectedFile, setSelectedFile] = useState(); - const [validData, setValidData] = useState([]); - const [parsedData, setParsedData] = useState([]); - - const { t } = useTranslation(); - const fileInputRef = useRef(null); - - const closeModal = () => { - setSelectedFile(undefined); - setFileData([]); - onClose?.(); - }; - - const onSelectFile = (file: Blob) => { - setSelectedFile(file); - dragProps.setFileDropError(""); - try { - const reader = new FileReader(); - - reader.onload = (e) => { - const result = (e.target as FileReader).result; - const workbook = XLSX.read(result, { - type: "binary", - cellDates: true, - }); - const worksheetName = workbook.SheetNames[0]; - const worksheet = workbook.Sheets[worksheetName]; - const data = XLSX.utils.sheet_to_json(worksheet, { defval: "" }); - //converts the date to string - data.forEach((row: any) => { - Object.keys(row).forEach((key) => { - if (row[key] instanceof Date) { - row[key] = row[key].toISOString().split("T")[0]; - } - }); - }); - - setFileData(data); - }; - reader.onerror = () => { - throw new Error("Error in reading file"); - }; - - reader.readAsBinaryString(file); - } catch (e: any) { - toast.error(e.message); - } - }; - - useEffect(() => { - if (fileData.length !== 0) { - const { errors, parsedData, ParsedDataWithOutErrors } = schemaParser( - fileData, - schema, - ); - setErrors(errors); - setParsedData(parsedData); - setValidData(ParsedDataWithOutErrors); - if (ParsedDataWithOutErrors.length !== 0) { - setIsValid?.(true); - } - } - }, [fileData]); - - const dragProps = useDragAndDrop(); - const onDrop = (e: React.DragEvent) => { - e.preventDefault(); - dragProps.setDragOver(false); - - const droppedFiles = e?.dataTransfer?.files; - - if (!droppedFiles || droppedFiles.length === 0) { - return dragProps.setFileDropError("Please drop a file to upload!"); - } - - const droppedFile = droppedFiles[0]; - const fileTypes = [ - "vnd.ms-excel", - "text/csv", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ]; - - if (!fileTypes.includes(droppedFile?.type)) { - dragProps.setFileDropError("Please drop a Excel / CSV file to upload!"); - setSelectedFile(null); - return; - } - onSelectFile(droppedFile); - }; - - return ( -
- {preview && ( - setPreview(false)} - selectedFile={selectedFile} - fileData={fileData} - errors={errors} - handleSubmit={handleSubmit} - parsedData={parsedData} - /> - )} -
!selectedFile && fileInputRef.current?.click()} - className={`mb-8 mt-5 flex flex-1 flex-col items-center justify-center rounded-lg border-[3px] border-dashed px-3 py-6 ${ - dragProps.dragOver ? "border-primary-500" : "border-secondary-500" - } ${dragProps.fileDropError !== "" ? "border-red-500" : ""}`} - > - -

- {dragProps.fileDropError !== "" && dragProps.fileDropError} - {!selectedFile && "Drag & drop xlsx/csv file to upload"} -

- { - const files = e.target.files; - if (files && files.length > 0) { - onSelectFile(files[0]); - } - }} - className="hidden" - ref={fileInputRef} - /> - {selectedFile && ( -
-

- {selectedFile.name} - {(selectedFile.size / 1024).toFixed(2)} KB -

- { - setSelectedFile(undefined); - setFileData([]); - setErrors([]); - setValidData([]); - if (setIsValid) setIsValid(false); - dragProps.setDragOver(false); - dragProps.setFileDropError(""); - }} - > - cancel - -
- )} - {selectedFile ? ( - <> - setPreview(true)} - > - - - ) : ( - e.stopPropagation()} - > - - )} -
-
- -
- - -
-
- ); -} diff --git a/src/components/Common/ExcelViewer.tsx b/src/components/Common/ExcelViewer.tsx deleted file mode 100644 index e77dc0132da..00000000000 --- a/src/components/Common/ExcelViewer.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import { t } from "i18next"; -import { ReactNode, useEffect, useState } from "react"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { Button } from "@/components/ui/button"; - -import DialogModal from "@/components/Common/Dialog"; -import Pagination from "@/components/Common/Pagination"; - -import { ParsedData } from "@/common/schemaParser"; - -type FilePreviewProps = { - title?: ReactNode; - description?: ReactNode; - show: boolean; - onClose: () => void; - selectedFile: { name: string }; - fileData?: any[]; - downloadURL?: string; - className?: string; - errors?: { index: number; key: string; error: string }[]; - handleSubmit: (data: ParsedData[]) => void; - showCheckbox?: boolean; - parsedData: ParsedData[]; -}; - -const ExcelViewer = ({ - title = "File Preview", - description, - show, - onClose, - className, - selectedFile, - fileData, - downloadURL, - showCheckbox = true, - handleSubmit, - parsedData, - errors = [], -}: FilePreviewProps) => { - const [currentPage, setCurrentPage] = useState(1); - const [rowsPerPage, setRowsPerPage] = useState(10); - const [selectedRowsData, setSelectedRowsData] = useState([]); - - const initialSelectedRows = fileData - ? (fileData - .map((_, i) => i) - .filter((i) => !errors.some((err) => err.index === i)) ?? []) - : []; - - const [selectedRows, setSelectedRows] = - useState(initialSelectedRows); - - const handleClose = () => { - onClose?.(); - }; - - useEffect(() => { - setSelectedRowsData(selectedRows.map((rowIndex) => parsedData[rowIndex])); - }, [selectedRows]); - - return ( - { - handleClose(); - }} - title={title} - description={description} - show={show} - > - <> -
-

- {selectedFile.name} -

-
- {downloadURL && downloadURL.length > 0 && ( - - )} -
-
-
-
- {fileData && fileData[0] ? ( -
- - - - {showCheckbox && ( - - )} - {Object.keys(fileData[0]).map((key) => ( - - ))} - - - - {fileData - .slice( - (currentPage - 1) * rowsPerPage, - (currentPage - 1) * rowsPerPage + rowsPerPage, - ) - .map((row, rowIndex) => { - const currentRowIndex = - rowIndex + (currentPage - 1) * rowsPerPage; - return ( - - {showCheckbox && ( - - )} - {Object.entries(row).map( - ([key, value]: [string, any], colIndex) => { - const error = errors.find( - (err) => - err.index === currentRowIndex && - err.key == key, - ); - - return ( - <> - - - ); - }, - )} - - ); - })} - -
- 0} - onChange={(e) => { - if (e.target.checked) { - setSelectedRows(fileData.map((_, i) => i)); - } else { - setSelectedRows([]); - } - }} - /> - - {key} -
- {errors.some( - (err) => err.index === currentRowIndex, - ) ? ( - err.index === currentRowIndex, - ) - .map((err) => err.key) - .join(", ")} - `} - > - - - ) : ( - err.index === currentRowIndex, - )} - onChange={() => { - if ( - selectedRows.includes(currentRowIndex) - ) { - setSelectedRows( - selectedRows.filter( - (i) => i !== currentRowIndex, - ), - ); - } else { - setSelectedRows([ - ...selectedRows, - currentRowIndex, - ]); - } - }} - className="disabled:bg-danger-500" - /> - )} - - {String(value)} -
-
- ) : ( -
No data found
- )} -
- {fileData && fileData.length > 5 && ( -
-

- Showing {currentPage * rowsPerPage - rowsPerPage + 1} to{" "} - {currentPage * rowsPerPage > fileData.length - ? fileData.length - : currentPage * rowsPerPage}{" "} - of {fileData.length} entries -

- {rowsPerPage < fileData.length && ( - { - setCurrentPage(page); - setRowsPerPage(rowsPerPage); - }} - /> - )} - -
- )} -
- -
- - -
-
- ); -}; - -export default ExcelViewer; diff --git a/src/components/Common/Export.tsx b/src/components/Common/Export.tsx deleted file mode 100644 index 6f9c6bdbb7d..00000000000 --- a/src/components/Common/Export.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { cn } from "@/lib/utils"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { Button } from "@/components/ui/button"; - -import useExport from "@/hooks/useExport"; - -import request from "@/Utils/request/request"; -import { ApiRoute } from "@/Utils/request/types"; - -interface ExportButtonProps { - disabled?: boolean | undefined; - tooltip?: string | undefined; - tooltipClassName?: string; - type?: "csv" | "json"; - action?: Parameters["exportFile"]>[0]; - route?: ApiRoute; - parse?: (data: string) => string; - filenamePrefix: string; - className?: string; - variant?: "primary_gradient" | "secondary"; -} - -export const ExportButton = ({ - tooltipClassName = "tooltip-bottom -translate-x-7", - variant, - type = "csv", - className, - parse, - ...props -}: ExportButtonProps) => { - const { isExporting, exportFile } = useExport(); - const { t } = useTranslation(); - - return ( - <> - - - ); -}; diff --git a/src/components/Facility/ConsultationDetails/ObservationsList.tsx b/src/components/Facility/ConsultationDetails/ObservationsList.tsx index e25002bf7cd..980a6a1c0d6 100644 --- a/src/components/Facility/ConsultationDetails/ObservationsList.tsx +++ b/src/components/Facility/ConsultationDetails/ObservationsList.tsx @@ -7,6 +7,8 @@ import CareIcon from "@/CAREUI/icons/CareIcon"; import { Card } from "@/components/ui/card"; +import { formatValue } from "@/components/Facility/ConsultationDetails/QuestionnaireResponsesList"; + import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { HTTPError } from "@/Utils/request/types"; @@ -96,7 +98,7 @@ export default function ObservationsList(props: Props) { )} {item.value.value && (
- {item.value.value} + {formatValue(item.value.value, item.value_type)}
)} {item.note && ( @@ -108,7 +110,7 @@ export default function ObservationsList(props: Props) { {hasNextPage && (
- {isFetchingNextPage ? t("loading_more") : t("load_more")} + {isFetchingNextPage ? t("loading") : t("load_more")}
)} diff --git a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx index 73a909330c6..a9cb31b9307 100644 --- a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx +++ b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx @@ -1,15 +1,21 @@ +import { useQuery } from "@tanstack/react-query"; import { t } from "i18next"; +import { useQueryParams } from "raviger"; import { useTranslation } from "react-i18next"; -import PaginatedList from "@/CAREUI/misc/PaginatedList"; +import { cn } from "@/lib/utils"; import { Badge } from "@/components/ui/badge"; import { Card } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; +import PaginationComponent from "@/components/Common/Pagination"; import { CardListSkeleton } from "@/components/Common/SkeletonLoading"; +import { RESULTS_PER_PAGE_LIMIT } from "@/common/constants"; + import routes from "@/Utils/request/api"; +import query from "@/Utils/request/query"; import { formatDateTime, properCase } from "@/Utils/utils"; import { AllergyIntoleranceRequest } from "@/types/emr/allergyIntolerance/allergyIntolerance"; import { DiagnosisRequest } from "@/types/emr/diagnosis/diagnosis"; @@ -53,7 +59,10 @@ interface QuestionResponseProps { }; } -function formatValue(value: ResponseValueType["value"], type: string): string { +export function formatValue( + value: ResponseValueType["value"], + type: string, +): string { if (!value) return ""; // Handle complex objects @@ -275,44 +284,67 @@ export default function QuestionnaireResponsesList({ patientId, }: Props) { const { t } = useTranslation(); + const [qParams, setQueryParams] = useQueryParams<{ page?: number }>(); + + const { data: questionnarieResponses, isLoading } = useQuery({ + queryKey: ["questionnaireResponses", patientId, qParams], + queryFn: query(routes.getQuestionnaireResponses, { + pathParams: { patientId }, + queryParams: { + encounter: encounter?.id, + limit: RESULTS_PER_PAGE_LIMIT, + offset: ((qParams.page ?? 1) - 1) * RESULTS_PER_PAGE_LIMIT, + }, + }), + }); return ( - - {() => ( -
+
+
+ {isLoading ? ( +
+ +
+ ) : (
- + {questionnarieResponses?.results?.length === 0 ? (
{t("no_questionnaire_responses")}
-
- - -
- -
-
- - className="grid gap-4"> - {(item) => } - - -
- -
+ ) : ( +
    + {questionnarieResponses?.results?.map( + (item: QuestionnaireResponse) => ( +
  • + +
  • + ), + )} +
    +
    + RESULTS_PER_PAGE_LIMIT + ? "visible" + : "invisible", + )} + > + setQueryParams({ page })} + /> +
    +
    +
+ )}
-
- )} - + )} +
+
); } diff --git a/src/components/Facility/FacilityHome.tsx b/src/components/Facility/FacilityHome.tsx index 4856911d5e7..be79cce3c63 100644 --- a/src/components/Facility/FacilityHome.tsx +++ b/src/components/Facility/FacilityHome.tsx @@ -251,7 +251,7 @@ export const FacilityHome = ({ facilityId }: Props) => {
- + + )} +
+ ); + })} + {isCurrentSlot && medication.status === "active" && ( + + )} + + ); + })} + +
+ {ACTIVE_STATUSES.includes( + medication.status as (typeof ACTIVE_STATUSES)[number], + ) && ( + + + + + + + + + )} +
+ + ); +}; + +export const AdministrationTab: React.FC = ({ + patientId, + encounterId, +}) => { + const currentDate = new Date(); + const [endSlotDate, setEndSlotDate] = useState(currentDate); + const [showStopped, setShowStopped] = useState(false); + const [endSlotIndex, setEndSlotIndex] = useState( + Math.floor(currentDate.getHours() / 6), + ); + // Calculate visible slots based on end slot + const visibleSlots = React.useMemo(() => { + const slots = []; + let currentIndex = endSlotIndex; + let currentDate = new Date(endSlotDate); + + // Add slots from right to left + for (let i = 0; i < 4; i++) { + if (currentIndex < 0) { + currentIndex = 3; + currentDate = new Date(currentDate); + currentDate.setDate(currentDate.getDate() - 1); + } + slots.unshift({ + ...TIME_SLOTS[currentIndex], + date: new Date(currentDate), + }); + currentIndex--; + } + return slots; + }, [endSlotDate, endSlotIndex]); + + // Queries + const { data: activeMedications, refetch: refetchActive } = useQuery({ + queryKey: ["medication_requests_active", patientId], + queryFn: query(medicationRequestApi.list, { + pathParams: { patientId }, + queryParams: { + encounter: encounterId, + limit: 100, + status: ACTIVE_STATUSES.join(","), + }, + }), + enabled: !!patientId, + }); + + const { data: stoppedMedications, refetch: refetchStopped } = useQuery({ + queryKey: ["medication_requests_stopped", patientId], + queryFn: query(medicationRequestApi.list, { + pathParams: { patientId }, + queryParams: { + encounter: encounterId, + limit: 100, + status: INACTIVE_STATUSES.join(","), + }, + }), + enabled: !!patientId, + }); + + const { data: administrations, refetch: refetchAdministrations } = useQuery({ + queryKey: ["medication_administrations", patientId, visibleSlots], + queryFn: query(routes.medicationAdministration.list, { + pathParams: { patientId }, + queryParams: { + encounter: encounterId, + limit: 100, + ...(visibleSlots.length > 0 && { + occurrence_period_start_after: (() => { + const firstSlot = visibleSlots[0]; + const [startHour] = firstSlot.start.split(":").map(Number); + const date = new Date(firstSlot.date); + date.setHours(startHour, 0, 0, 0); + return format(date, "yyyy-MM-dd'T'HH:mm:ss"); + })(), + occurrence_period_start_before: (() => { + const lastSlot = visibleSlots[visibleSlots.length - 1]; + const [endHour] = lastSlot.end.split(":").map(Number); + const date = new Date(lastSlot.date); + date.setHours(endHour, 0, 0, 0); + return format(date, "yyyy-MM-dd'T'HH:mm:ss"); + })(), + }), + }, + }), + enabled: !!patientId && !!visibleSlots?.length, + }); + + // Get last administered date and last administered by for each medication + const lastAdministeredDetails = React.useMemo(() => { + return administrations?.results?.reduce<{ + dates: Record; + performers: Record; + }>( + (acc, admin) => { + const existingDate = acc.dates[admin.request]; + const adminDate = new Date(admin.occurrence_period_start); + + if (!existingDate || adminDate > new Date(existingDate)) { + acc.dates[admin.request] = admin.occurrence_period_start; + acc.performers[admin.request] = admin.created_by + ? formatName(admin.created_by) + : ""; + } + + return acc; + }, + { dates: {}, performers: {} }, + ); + }, [administrations?.results]); + + // Calculate earliest authored date from all medications + const getEarliestAuthoredDate = (medications: MedicationRequestRead[]) => { + if (!medications?.length) return null; + return new Date( + Math.min( + ...medications.map((med) => + new Date(med.authored_on || med.created_date).getTime(), + ), + ), + ); + }; + + // Calculate if we can go back further based on the earliest slot and authored date + const canGoBack = React.useMemo(() => { + const medications = showStopped + ? [ + ...(activeMedications?.results || []), + ...(stoppedMedications?.results || []), + ] + : activeMedications?.results || []; + + const earliestAuthoredDate = getEarliestAuthoredDate(medications); + if (!earliestAuthoredDate || !visibleSlots.length) return true; + + const firstSlotDate = new Date(visibleSlots[0].date); + const [startHour] = visibleSlots[0].start.split(":").map(Number); + firstSlotDate.setHours(startHour, 0, 0, 0); + + return firstSlotDate > earliestAuthoredDate; + }, [activeMedications, stoppedMedications, showStopped, visibleSlots]); + + // State for administration + const [selectedMedication, setSelectedMedication] = + useState(null); + const [dialogOpen, setDialogOpen] = useState(false); + const [isSheetOpen, setIsSheetOpen] = useState(false); + const [administrationRequest, setAdministrationRequest] = + useState(null); + + // Calculate last modified date + const lastModifiedDate = React.useMemo(() => { + if (!administrations?.results?.length) return null; + + const sortedAdmins = [...administrations.results].sort( + (a, b) => + new Date(b.occurrence_period_start).getTime() - + new Date(a.occurrence_period_start).getTime(), + ); + + return new Date(sortedAdmins[0].occurrence_period_start); + }, [administrations?.results]); + + // Mutations + const { mutate: discontinueMedication } = useMutation({ + mutationFn: mutate(medicationRequestApi.upsert, { + pathParams: { patientId }, + }), + onSuccess: () => { + refetchActive(); + refetchStopped(); + }, + }); + + // Handlers + const handlePreviousSlot = React.useCallback(() => { + if (!canGoBack) return; + + const newEndSlotIndex = endSlotIndex - 1; + if (newEndSlotIndex < 0) { + setEndSlotIndex(3); + const newDate = new Date(endSlotDate); + newDate.setDate(newDate.getDate() - 1); + setEndSlotDate(newDate); + } else { + setEndSlotIndex(newEndSlotIndex); + } + }, [endSlotDate, endSlotIndex, canGoBack]); + + const handleNextSlot = React.useCallback(() => { + const newEndSlotIndex = endSlotIndex + 1; + if (newEndSlotIndex > 3) { + setEndSlotIndex(0); + const newDate = new Date(endSlotDate); + newDate.setDate(newDate.getDate() + 1); + setEndSlotDate(newDate); + } else { + setEndSlotIndex(newEndSlotIndex); + } + }, [endSlotDate, endSlotIndex]); + + const handleAdminister = React.useCallback( + (medication: MedicationRequestRead) => { + setAdministrationRequest( + createMedicationAdministrationRequest(medication, encounterId), + ); + setSelectedMedication(medication); + setDialogOpen(true); + }, + [encounterId], + ); + + const handleEditAdministration = React.useCallback( + (medication: MedicationRequestRead, admin: MedicationAdministration) => { + setAdministrationRequest({ + id: admin.id, + request: admin.request, + encounter: admin.encounter, + note: admin.note || "", + occurrence_period_start: admin.occurrence_period_start, + occurrence_period_end: admin.occurrence_period_end, + status: admin.status, + medication: admin.medication, + dosage: admin.dosage, + }); + setSelectedMedication(medication); + setDialogOpen(true); + }, + [], + ); + + const handleDiscontinue = React.useCallback( + (medication: MedicationRequestRead) => { + discontinueMedication({ + datapoints: [ + { + ...medication, + status: "ended", + encounter: encounterId, + }, + ], + }); + }, + [discontinueMedication, encounterId], + ); + + const medications = showStopped + ? [ + ...(activeMedications?.results || []), + ...(stoppedMedications?.results || []), + ] + : activeMedications?.results || []; + + if (!activeMedications || !stoppedMedications) { + return ( +
+ +
+ ); + } + + if (!medications?.length) { + return ( + + ); + } + + return ( +
+
+ +
+ + + +
+ {/* Top row without vertical borders */} +
+
+
+ {lastModifiedDate && ( +
+ {t("last_modified")}{" "} + {formatDistanceToNow(lastModifiedDate)} {t("ago")} +
+ )} +
+
+ +
+
+ {visibleSlots.map((slot) => ( + + ))} +
+ +
+
+ + {/* Main content with borders */} +
+ {/* Headers */} +
+ {t("medicine")}: +
+ {visibleSlots.map((slot, i) => ( +
+ {i === endSlotIndex && + slot.date.getTime() === currentDate.getTime() && ( +
+
+
+ )} + {slot.label} +
+ ))} +
+ + {/* Medication rows */} + {medications?.map((medication) => ( + + ))} +
+
+ + {stoppedMedications?.results?.length > 0 && ( +
setShowStopped(!showStopped)} + > + + + {showStopped ? t("hide") : t("show")}{" "} + {`${stoppedMedications?.results?.length} ${t("stopped")}`}{" "} + {t("prescriptions")} + +
+ )} + + + + + {selectedMedication && administrationRequest && ( + { + setDialogOpen(open); + if (!open) { + setAdministrationRequest(null); + setSelectedMedication(null); + refetchAdministrations(); + } + }} + medication={selectedMedication} + lastAdministeredDate={ + lastAdministeredDetails?.dates[selectedMedication.id] + } + lastAdministeredBy={ + lastAdministeredDetails?.performers[selectedMedication.id] + } + administrationRequest={administrationRequest} + patientId={patientId} + /> + )} + + { + setIsSheetOpen(open); + if (!open) { + refetchAdministrations(); + } + }} + medications={medications} + lastAdministeredDates={lastAdministeredDetails?.dates} + patientId={patientId} + encounterId={encounterId} + /> +
+ ); +}; diff --git a/src/components/Medicine/MedicationAdministration/MedicineAdminDialog.tsx b/src/components/Medicine/MedicationAdministration/MedicineAdminDialog.tsx new file mode 100644 index 00000000000..3668e4f84de --- /dev/null +++ b/src/components/Medicine/MedicationAdministration/MedicineAdminDialog.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { useMutation } from "@tanstack/react-query"; +import { t } from "i18next"; +import React from "react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; + +import mutate from "@/Utils/request/mutate"; +import { MedicationAdministrationRequest } from "@/types/emr/medicationAdministration/medicationAdministration"; +import medicationAdministrationApi from "@/types/emr/medicationAdministration/medicationAdministrationApi"; +import { MedicationRequestRead } from "@/types/emr/medicationRequest"; + +import { MedicineAdminForm } from "./MedicineAdminForm"; + +interface Props { + open: boolean; + onOpenChange: (open: boolean) => void; + medication: MedicationRequestRead; + lastAdministeredDate?: string; + lastAdministeredBy?: string; + administrationRequest: MedicationAdministrationRequest; + patientId: string; +} + +export const MedicineAdminDialog = ({ + open, + onOpenChange, + medication, + lastAdministeredDate, + lastAdministeredBy, + administrationRequest: initialRequest, + patientId, +}: Props) => { + const [administrationRequest, setAdministrationRequest] = + React.useState(initialRequest); + const [isFormValid, setIsFormValid] = React.useState(true); + + // Update state when initialRequest changes + React.useEffect(() => { + setAdministrationRequest(initialRequest); + }, [initialRequest]); + + const { mutate: upsertAdministration, isPending } = useMutation({ + mutationFn: mutate( + medicationAdministrationApi.upsertMedicationAdministration, + { + pathParams: { patientId: patientId }, + }, + ), + onSuccess: () => { + onOpenChange(false); + toast.success(t("medication_administration_saved")); + }, + }); + + const handleSubmit = () => { + upsertAdministration({ + datapoints: [administrationRequest], + }); + }; + + return ( + + + +
+ + {administrationRequest.id + ? t("edit_administration") + : t("administer_medicine")} + +
+
+ +
+ +
+ + + + + +
+
+ ); +}; diff --git a/src/components/Medicine/MedicationAdministration/MedicineAdminForm.tsx b/src/components/Medicine/MedicationAdministration/MedicineAdminForm.tsx new file mode 100644 index 00000000000..62b48d65b65 --- /dev/null +++ b/src/components/Medicine/MedicationAdministration/MedicineAdminForm.tsx @@ -0,0 +1,422 @@ +"use client"; + +import { format, formatDistanceToNow } from "date-fns"; +import { t } from "i18next"; +import React, { useEffect, useState } from "react"; + +import { cn } from "@/lib/utils"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import { Button } from "@/components/ui/button"; +import { Calendar } from "@/components/ui/calendar"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +import { getFrequencyDisplay } from "@/components/Medicine/MedicationsTable"; +import { formatDosage } from "@/components/Medicine/utils"; + +import { formatName } from "@/Utils/utils"; +import { + MEDICATION_ADMINISTRATION_STATUS, + MedicationAdministrationRequest, + MedicationAdministrationStatus, +} from "@/types/emr/medicationAdministration/medicationAdministration"; +import { MedicationRequestRead } from "@/types/emr/medicationRequest"; + +interface MedicineAdminFormProps { + medication: MedicationRequestRead; + lastAdministeredDate?: string; + lastAdministeredBy?: string; + administrationRequest: MedicationAdministrationRequest; + onChange: (request: MedicationAdministrationRequest) => void; + formId: string; + isValid?: (valid: boolean) => void; +} + +export const MedicineAdminForm: React.FC = ({ + medication, + lastAdministeredDate, + lastAdministeredBy, + administrationRequest, + onChange, + formId, + isValid, +}) => { + const [isPastTime, setIsPastTime] = useState( + administrationRequest.occurrence_period_start !== + administrationRequest.occurrence_period_end || !!administrationRequest.id, + ); + const [startTimeError, setStartTimeError] = useState(""); + const [endTimeError, setEndTimeError] = useState(""); + + const validateDateTime = (date: Date, isStartTime: boolean): string => { + const now = new Date(); + const authoredOn = new Date(medication.authored_on); + const startTime = new Date(administrationRequest.occurrence_period_start); + + if (date > now) { + return t( + isStartTime ? "start_time_future_error" : "end_time_future_error", + ); + } + + if (isStartTime) { + return date < authoredOn ? t("start_time_before_authored_error") : ""; + } + + return date < startTime ? t("end_time_before_start_error") : ""; + }; + + // Validate and notify parent whenever times change + useEffect(() => { + if ( + !administrationRequest.occurrence_period_start || + !administrationRequest.occurrence_period_end + ) { + isValid?.(false); + return; + } + + const startDate = new Date(administrationRequest.occurrence_period_start); + const endDate = new Date(administrationRequest.occurrence_period_end); + + const startError = validateDateTime(startDate, true); + const endError = validateDateTime(endDate, false); + + setStartTimeError(startError); + setEndTimeError(endError); + + isValid?.(!startError && !endError); + }, [ + administrationRequest.occurrence_period_start, + administrationRequest.occurrence_period_end, + isValid, + validateDateTime, + ]); + + const handleDateChange = (newTime: string, isStartTime: boolean) => { + const date = new Date(newTime); + + // Preserve existing time if available + const existingDateStr = isStartTime + ? administrationRequest.occurrence_period_start + : administrationRequest.occurrence_period_end; + + if (existingDateStr) { + const existingDate = new Date(existingDateStr); + date.setHours(existingDate.getHours()); + date.setMinutes(existingDate.getMinutes()); + } + + onChange({ + ...administrationRequest, + ...(isStartTime + ? { + occurrence_period_start: date.toISOString(), + occurrence_period_end: date.toISOString(), + } + : { + occurrence_period_end: date.toISOString(), + }), + }); + }; + + const formatTime = (date: string | undefined) => { + if (!date) return ""; + try { + const dateObj = new Date(date); + if (isNaN(dateObj.getTime())) return ""; + return `${dateObj.getHours().toString().padStart(2, "0")}:${dateObj + .getMinutes() + .toString() + .padStart(2, "0")}`; + } catch { + return ""; + } + }; + + const handleTimeChange = ( + event: React.ChangeEvent, + isStartTime: boolean, + ) => { + const [hours, minutes] = event.target.value.split(":").map(Number); + if (isNaN(hours) || isNaN(minutes)) return; + + const dateStr = isStartTime + ? administrationRequest.occurrence_period_start + : administrationRequest.occurrence_period_end; + + if (!dateStr) return; + + const currentDate = new Date(dateStr); + if (isNaN(currentDate.getTime())) return; + + currentDate.setHours(hours); + currentDate.setMinutes(minutes); + + onChange({ + ...administrationRequest, + ...(isStartTime + ? { + occurrence_period_start: currentDate.toISOString(), + occurrence_period_end: currentDate.toISOString(), + } + : { + occurrence_period_end: currentDate.toISOString(), + }), + }); + }; + + return ( +
+
+

+ {medication.medication?.display} +

+ {lastAdministeredDate && ( +

+ {t("last_administered")}{" "} + {formatDistanceToNow(new Date(lastAdministeredDate))} {t("ago")}{" "} + {t("by")} {formatName(medication.created_by)} +

+ )} +

+ {t("prescribed")}{" "} + {formatDistanceToNow( + new Date(medication.authored_on || medication.created_date), + )}{" "} + {t("ago")} {t("by")} {lastAdministeredBy} +

+
+ +
+
+ +

+ {formatDosage(medication.dosage_instruction[0])} +

+
+
+ +

+ {getFrequencyDisplay(medication.dosage_instruction[0]?.timing) + ?.meaning || "-"} +

+
+
+ +

+ {medication.dosage_instruction[0]?.route?.display || "Oral"} +

+
+
+ +

+ {medication.dosage_instruction[0]?.timing?.repeat?.bounds_duration + ?.value || "-"}{" "} + {medication.dosage_instruction[0]?.timing?.repeat?.bounds_duration + ?.unit || ""} +

+
+
+ +
+ + +
+ +
+ + + onChange({ ...administrationRequest, note: e.target.value }) + } + /> +
+ + {!administrationRequest.id && ( +
+ + { + setIsPastTime(newValue === "yes"); + if (newValue === "no") { + // Set both times to current time + const now = new Date().toISOString(); + onChange({ + ...administrationRequest, + occurrence_period_start: now, + occurrence_period_end: now, + }); + } + }} + className="flex gap-4" + > +
+ + +
+
+ + +
+
+
+ )} + +
+ +
+ + + + + + { + if (!date) return; + handleDateChange(date.toISOString(), true); + }} + initialFocus + disabled={(date) => { + const now = new Date(); + const encounterStart = new Date(medication.authored_on); + return date < encounterStart || date > now; + }} + /> + + + handleTimeChange(e, true)} + disabled={!isPastTime || !!administrationRequest.id} + /> +
+ {startTimeError && ( +

{startTimeError}

+ )} +
+ +
+ +
+ + + + + + { + if (!date) return; + handleDateChange(date.toISOString(), false); + }} + initialFocus + disabled={(date) => { + const now = new Date(); + const encounterStart = new Date(medication.authored_on); + return date < encounterStart || date > now; + }} + /> + + + handleTimeChange(e, false)} + disabled={ + !isPastTime || + (!!administrationRequest.id && + administrationRequest.status !== "in_progress") + } + /> +
+ {endTimeError &&

{endTimeError}

} +
+
+ ); +}; diff --git a/src/components/Medicine/MedicationAdministration/MedicineAdminSheet.tsx b/src/components/Medicine/MedicationAdministration/MedicineAdminSheet.tsx new file mode 100644 index 00000000000..80b212081c4 --- /dev/null +++ b/src/components/Medicine/MedicationAdministration/MedicineAdminSheet.tsx @@ -0,0 +1,287 @@ +"use client"; + +import { useMutation } from "@tanstack/react-query"; +import { t } from "i18next"; +import { Search } from "lucide-react"; +import React, { useCallback, useMemo, useRef, useState } from "react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { + Sheet, + SheetContent, + SheetFooter, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; + +import mutate from "@/Utils/request/mutate"; +import { MedicationAdministrationRequest } from "@/types/emr/medicationAdministration/medicationAdministration"; +import medicationAdministrationApi from "@/types/emr/medicationAdministration/medicationAdministrationApi"; +import { MedicationRequestRead } from "@/types/emr/medicationRequest"; + +import { MedicineAdminForm } from "./MedicineAdminForm"; +import { createMedicationAdministrationRequest } from "./utils"; + +interface Props { + open: boolean; + onOpenChange: (open: boolean) => void; + medications: MedicationRequestRead[]; + lastAdministeredDates?: Record; + patientId: string; + encounterId: string; +} + +interface MedicineListItemProps { + medicine: MedicationRequestRead; + isSelected: boolean; + onSelect: (checked: boolean) => void; + administrationRequest?: MedicationAdministrationRequest; + lastAdministeredDate?: string; + lastAdministeredBy?: string; + onAdministrationChange: (request: MedicationAdministrationRequest) => void; + isValid: (valid: boolean) => void; +} + +const MedicineListItem = ({ + medicine, + isSelected, + onSelect, + administrationRequest, + lastAdministeredDate, + lastAdministeredBy, + onAdministrationChange, + isValid, +}: MedicineListItemProps) => { + const medicationDisplay = medicine.medication?.display; + + return ( +
+
+
+
+ {medicationDisplay} + {medicine.dosage_instruction[0]?.as_needed_boolean && ( + + {t("as_needed_prn")} + + )} +
+
+ +
+ +
+
+ {isSelected && administrationRequest && ( + + )} +
+
+
+ ); +}; + +export function MedicineAdminSheet({ + open, + onOpenChange, + medications, + lastAdministeredDates, + patientId, + encounterId, +}: Props) { + const [selectedMedicines, setSelectedMedicines] = useState>( + new Set(), + ); + const [administrationRequests, setAdministrationRequests] = useState< + Record + >({}); + const [formValidation, setFormValidation] = useState>( + {}, + ); + const [search, setSearch] = useState(""); + const formRef = useRef(null); + + const { mutate: upsertAdministrations, isPending } = useMutation({ + mutationFn: mutate( + medicationAdministrationApi.upsertMedicationAdministration, + { + pathParams: { patientId }, + }, + ), + onSuccess: () => { + toast.success(t("medication_administration_saved")); + handleClose(); + }, + }); + + const filteredMedicines = medications.filter((medicine) => { + const display = medicine.medication?.display; + return ( + typeof display === "string" && + display.toLowerCase().includes(search.toLowerCase()) + ); + }); + + const handleSelect = useCallback( + (id: string, checked: boolean) => { + setSelectedMedicines((prev) => { + const next = new Set(prev); + if (checked) { + next.add(id); + const medicine = medications.find((m) => m.id === id); + if (medicine?.medication?.display) { + setAdministrationRequests((prev) => ({ + ...prev, + [id]: createMedicationAdministrationRequest( + medicine, + encounterId, + ), + })); + } + } else { + next.delete(id); + setAdministrationRequests((prev) => { + const { [id]: _, ...rest } = prev; + return rest; + }); + } + return next; + }); + }, + [medications, encounterId], + ); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const administrations = Array.from(selectedMedicines).map( + (id) => administrationRequests[id], + ); + upsertAdministrations({ + datapoints: administrations, + }); + }; + + const handleClose = () => { + onOpenChange(false); + setSelectedMedicines(new Set()); + setAdministrationRequests({}); + }; + + const handleAdministrationChange = useCallback( + (medicineId: string, request: MedicationAdministrationRequest) => { + setAdministrationRequests((prev) => ({ + ...prev, + [medicineId]: request, + })); + }, + [], + ); + + const handleFormValidation = useCallback( + (medicineId: string, isValid: boolean) => { + setFormValidation((prev) => ({ + ...prev, + [medicineId]: isValid, + })); + }, + [], + ); + + const isAllFormsValid = useMemo( + () => + Array.from(selectedMedicines).every((id) => formValidation[id] !== false), + [selectedMedicines, formValidation], + ); + + return ( + + +
+ + + {t("administer_medicines")} + +
+ + setSearch(e.target.value)} + className="pl-8" + /> +
+
+ +
+
+ {filteredMedicines.map((medicine) => ( + handleSelect(medicine.id, checked)} + administrationRequest={administrationRequests[medicine.id]} + lastAdministeredDate={lastAdministeredDates?.[medicine.id]} + onAdministrationChange={(request) => + handleAdministrationChange(medicine.id, request) + } + isValid={(valid) => handleFormValidation(medicine.id, valid)} + /> + ))} +
+
+ + +
+ + +
+
+
+
+
+ ); +} diff --git a/src/components/Medicine/MedicationAdministration/utils.ts b/src/components/Medicine/MedicationAdministration/utils.ts new file mode 100644 index 00000000000..377f2febb1d --- /dev/null +++ b/src/components/Medicine/MedicationAdministration/utils.ts @@ -0,0 +1,116 @@ +import { format } from "date-fns"; + +import { MedicationAdministrationRequest } from "@/types/emr/medicationAdministration/medicationAdministration"; +import { MedicationRequestRead } from "@/types/emr/medicationRequest"; + +// Constants +export const TIME_SLOTS = [ + { label: "12:00 AM - 06:00 AM", start: "00:00", end: "06:00" }, + { label: "06:00 AM - 12:00 PM", start: "06:00", end: "12:00" }, + { label: "12:00 PM - 06:00 PM", start: "12:00", end: "18:00" }, + { label: "06:00 PM - 12:00 AM", start: "18:00", end: "24:00" }, +] as const; + +export const STATUS_COLORS = { + completed: "bg-emerald-50 text-emerald-700 border-emerald-200", + in_progress: "bg-yellow-50 text-yellow-700 border-yellow-200", + default: "bg-red-50 text-red-700 border-red-200", +} as const; + +// Utility Functions +export function createMedicationAdministrationRequest( + medication: MedicationRequestRead, + encounterId: string, +): MedicationAdministrationRequest { + return { + request: medication.id, + encounter: encounterId, + medication: { + code: medication.medication?.code, + display: medication.medication?.display, + system: medication.medication?.system, + }, + occurrence_period_start: format(new Date(), "yyyy-MM-dd'T'HH:mm"), + occurrence_period_end: format(new Date(), "yyyy-MM-dd'T'HH:mm"), + note: "", + status: "completed", + dosage: { + site: medication.dosage_instruction[0]?.site, + route: medication.dosage_instruction[0]?.route, + method: medication.dosage_instruction[0]?.method, + dose: medication.dosage_instruction[0]?.dose_and_rate?.dose_quantity && { + value: + medication.dosage_instruction[0]?.dose_and_rate?.dose_quantity?.value, + unit: medication.dosage_instruction[0]?.dose_and_rate?.dose_quantity + ?.unit, + }, + }, + }; +} + +export function isTimeInSlot( + date: Date, + slot: { date: Date; start: string; end: string }, +): boolean { + const slotStartDate = new Date(slot.date); + const [startHour] = slot.start.split(":").map(Number); + const [endHour] = slot.end.split(":").map(Number); + + slotStartDate.setHours(startHour, 0, 0, 0); + const slotEndDate = new Date(slotStartDate); + slotEndDate.setHours(endHour, 0, 0, 0); + + return date >= slotStartDate && date < slotEndDate; +} + +export function getAdministrationsForTimeSlot< + T extends { + occurrence_period_start: string; + request: string; + }, +>( + administrations: T[], + medicationId: string, + slotDate: Date, + start: string, + end: string, +): T[] { + return administrations.filter((admin) => { + const adminDate = new Date(admin.occurrence_period_start); + const slotStartDate = new Date(slotDate); + const slotEndDate = new Date(slotDate); + + const [startHour] = start.split(":").map(Number); + const [endHour] = end.split(":").map(Number); + + slotStartDate.setHours(startHour, 0, 0, 0); + slotEndDate.setHours(endHour, 0, 0, 0); + + return ( + admin.request === medicationId && + adminDate >= slotStartDate && + adminDate < slotEndDate + ); + }); +} + +export function getCurrentTimeSlotIndex(): number { + const hour = new Date().getHours(); + if (hour < 6) return 0; + if (hour < 12) return 1; + if (hour < 18) return 2; + return 3; +} + +export function getEarliestAuthoredDate( + medications: MedicationRequestRead[], +): Date | null { + if (!medications?.length) return null; + return new Date( + Math.min( + ...medications.map((med) => + new Date(med.authored_on || med.created_date).getTime(), + ), + ), + ); +} diff --git a/src/components/Medicine/MedicationRequestTable/index.tsx b/src/components/Medicine/MedicationRequestTable/index.tsx index 92210bf98b9..389093ae429 100644 --- a/src/components/Medicine/MedicationRequestTable/index.tsx +++ b/src/components/Medicine/MedicationRequestTable/index.tsx @@ -1,23 +1,54 @@ import { useQuery } from "@tanstack/react-query"; +import { t } from "i18next"; import { PencilIcon } from "lucide-react"; import { Link } from "raviger"; import { useState } from "react"; -import SubHeading from "@/CAREUI/display/SubHeading"; import CareIcon from "@/CAREUI/icons/CareIcon"; -import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import Loading from "@/components/Common/Loading"; +import { AdministrationTab } from "@/components/Medicine/MedicationAdministration/AdministrationTab"; import { MedicationsTable } from "@/components/Medicine/MedicationsTable"; import query from "@/Utils/request/query"; import { MedicationRequestRead } from "@/types/emr/medicationRequest"; import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi"; +interface EmptyStateProps { + searching?: boolean; + searchQuery?: string; + message?: string; + description?: string; +} + +export const EmptyState = ({ + searching, + searchQuery, + message, + description, +}: EmptyStateProps) => ( +
+
+ +
+
+

+ {message || (searching ? "No matches found" : "No Prescriptions")} +

+

+ {description || + (searching + ? `No medications match "${searchQuery}"` + : "No medications have been prescribed yet")} +

+
+
+); + interface Props { readonly?: boolean; facilityId: string; @@ -26,22 +57,49 @@ interface Props { } export default function MedicationRequestTable({ - facilityId, patientId, encounterId, + facilityId, }: Props) { const [searchQuery, setSearchQuery] = useState(""); + const [showStopped, setShowStopped] = useState(false); + + const { data: activeMedications, isLoading: loadingActive } = useQuery({ + queryKey: ["medication_requests_active", patientId], + queryFn: query(medicationRequestApi.list, { + pathParams: { patientId: patientId }, + queryParams: { + encounter: encounterId, + limit: 100, + status: ["active", "on-hold", "draft", "unknown"].join(","), + }, + }), + enabled: !!patientId, + }); - const { data: medications, isLoading: loading } = useQuery({ - queryKey: ["medication_requests", patientId], + const { data: stoppedMedications, isLoading: loadingStopped } = useQuery({ + queryKey: ["medication_requests_stopped", patientId], queryFn: query(medicationRequestApi.list, { pathParams: { patientId: patientId }, - queryParams: { encounter: encounterId }, + queryParams: { + encounter: encounterId, + limit: 100, + status: ["ended", "completed", "cancelled", "entered_in_error"].join( + ",", + ), + }, }), enabled: !!patientId, }); - const filteredMedications = medications?.results?.filter( + const medications = showStopped + ? [ + ...(activeMedications?.results || []), + ...(stoppedMedications?.results || []), + ] + : activeMedications?.results || []; + + const displayedMedications = medications.filter( (med: MedicationRequestRead) => { if (!searchQuery.trim()) return true; const searchTerm = searchQuery.toLowerCase().trim(); @@ -50,123 +108,114 @@ export default function MedicationRequestTable({ }, ); - const activeMedications = filteredMedications?.filter( - (med: MedicationRequestRead) => - ["active", "on_hold"].includes(med.status || ""), - ); - const discontinuedMedications = filteredMedications?.filter( - (med: MedicationRequestRead) => - !["active", "on_hold"].includes(med.status || ""), - ); - - const EmptyState = ({ searching }: { searching?: boolean }) => ( -
-
- -
-
-

- {searching ? "No matches found" : "No Prescriptions"} -

-

- {searching - ? `No medications match "${searchQuery}"` - : "No medications have been prescribed yet"} -

-
-
- ); + const isLoading = loadingActive || loadingStopped; return (
- - - -
- } - /> - -
-
- - setSearchQuery(e.target.value)} - className="flex-1 bg-transparent text-sm outline-none placeholder:text-gray-500" - /> - {searchQuery && ( - - )} -
- - {loading ? ( -
- -
- ) : !medications?.results?.length ? ( - - ) : !filteredMedications?.length ? ( - - ) : ( - - -
- - - Active{" "} - - {activeMedications?.length || 0} - - - - Discontinued{" "} - - {discontinuedMedications?.length || 0} - - - -
+ {t("prescriptions")} + + + {t("medicine_administration")} + + -
- - - - - +
+
+
+ + setSearchQuery(e.target.value)} + className="flex-1 bg-transparent text-sm outline-none placeholder:text-gray-500" /> - + {searchQuery && ( + + )} +
+
+ + +
- - - - )} + + {isLoading ? ( +
+ +
+ ) : !medications.length ? ( + + ) : !displayedMedications.length ? ( + + ) : ( + +
+
+ +
+ {!!stoppedMedications?.results?.length && ( +
setShowStopped(!showStopped)} + > + + + {showStopped ? t("hide") : t("show")}{" "} + {`${stoppedMedications?.results?.length} ${t("stopped")}`}{" "} + {t("prescriptions")} + +
+ )} +
+ +
+ )} +
+
+ + + + +
); diff --git a/src/components/Medicine/MedicationRequestTable/utils.ts b/src/components/Medicine/MedicationRequestTable/utils.ts deleted file mode 100644 index b32427c3a80..00000000000 --- a/src/components/Medicine/MedicationRequestTable/utils.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { MedicationRequest } from "@/types/emr/medicationRequest"; - -export function isMedicationDiscontinued(medicationRequest: MedicationRequest) { - return ["completed", "ended", "stopped", "cancelled"].includes( - medicationRequest.status!, - ); -} diff --git a/src/components/Medicine/MedicationsTable.tsx b/src/components/Medicine/MedicationsTable.tsx index 059e35bd4d4..05a8e82bada 100644 --- a/src/components/Medicine/MedicationsTable.tsx +++ b/src/components/Medicine/MedicationsTable.tsx @@ -19,7 +19,7 @@ import { import { formatDosage, formatSig } from "./utils"; -function getFrequencyDisplay( +export function getFrequencyDisplay( timing?: MedicationRequestDosageInstruction["timing"], ) { if (!timing) return undefined; diff --git a/src/components/Medicine/MultiValueSetSelect.tsx b/src/components/Medicine/MultiValueSetSelect.tsx index f80415b8254..c3da4742235 100644 --- a/src/components/Medicine/MultiValueSetSelect.tsx +++ b/src/components/Medicine/MultiValueSetSelect.tsx @@ -55,6 +55,7 @@ export function MultiValueSetSelect({ +
+
-
- -
- - - > - {(encounter) => } - -
- + setQueryParams({ page })} + /> +
+ + + )} - - )} - + )} + + ); }; diff --git a/src/components/Patient/PatientDetailsTab/PatientUsers.tsx b/src/components/Patient/PatientDetailsTab/PatientUsers.tsx index 5e6f00494ab..5f0e90e8297 100644 --- a/src/components/Patient/PatientDetailsTab/PatientUsers.tsx +++ b/src/components/Patient/PatientDetailsTab/PatientUsers.tsx @@ -103,7 +103,7 @@ function AddUserSheet({ patientId }: AddUserSheetProps) { return ( - @@ -114,7 +114,7 @@ function AddUserSheet({ patientId }: AddUserSheetProps) { {t("search_user_description")}
-
+

{t("search_user")}

+ +
+ + {id ? ( + + + Manage Organizations + + } + /> + ) : ( +
+
+ {selectedOrgIds.length > 0 ? ( + availableOrganizations?.results + .filter((org) => selectedOrgIds.includes(org.id)) + .map((org) => ( + + {org.name} + + + )) + ) : ( +

+ No organizations selected +

+ )} +
+ + + + + No organizations found. + + {isLoadingOrganizations ? ( +
+ +
+ ) : ( + availableOrganizations?.results.map((org) => ( + + handleToggleOrganization(org.id) + } + > +
+ + {org.name} + {org.description && ( + + - {org.description} + + )} +
+ {selectedOrgIds.includes(org.id) && ( + + )} +
+ )) + )} +
+
+
+
+ )} +
- {/* Main Content */}
@@ -337,6 +493,22 @@ export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { />
+
+ + + updateQuestionnaireField("slug", e.target.value) + } + placeholder="unique-identifier-for-questionnaire" + className="font-mono" + /> +

+ A unique URL-friendly identifier for this questionnaire +

+
+