From 46fbbcd3c4fb978839843856165e1550e378e6ce Mon Sep 17 00:00:00 2001 From: Paul Bui-Quang Date: Tue, 24 Sep 2024 17:27:18 +0200 Subject: [PATCH] feat: add criteria form builder --- .gitignore | 3 +- .storybook/main.ts | 40 + .storybook/preview.ts | 14 + package-lock.json | 1775 ++++++++++++++-- package.json | 23 +- .../cohortCreation/cohortCreation.test.ts | 130 +- .../data/cohortCreation/claimCriteria.ts | 33 +- .../data/cohortCreation/cohortCreation.ts | 109 +- .../data/cohortCreation/conditionCriteria.ts | 35 +- .../data/cohortCreation/documentCriteria.ts | 53 +- .../data/cohortCreation/encounterCriteria.ts | 47 +- .../data/cohortCreation/imagingCriteria.ts | 50 +- .../data/cohortCreation/medicationCriteria.ts | 46 +- .../cohortCreation/observationCriteria.ts | 41 +- .../data/cohortCreation/patientCriteria.ts | 28 +- .../data/cohortCreation/procedureCriteria.ts | 36 +- .../CreationCohort/ControlPanel/styles.ts | 2 +- .../CreationCohort/DataList_Criteria.tsx | 187 +- .../components/CriteriaCard/index.tsx | 8 +- .../AdvancedInputs/AdvancedInputs.tsx | 140 -- .../BiologySearch/BiologySearch.tsx | 2 +- .../components/Form/BiologyForm.tsx | 403 ---- .../components/Hierarchy/BiologyHierarchy.tsx | 4 +- .../CriteriaRightPanel/BiologyForm/index.tsx | 52 +- .../CCAM/components/Form/CCAMForm.tsx | 246 --- .../CCAM/components/Form/styles.ts | 53 - .../components/Hierarchy/CCAMHierarchy.tsx | 6 +- .../CriteriaRightPanel/CCAM/index.tsx | 52 +- .../Cim10Form/components/Form/Cim10Form.tsx | 261 --- .../Cim10Form/components/Form/styles.ts | 53 - .../components/Hierarchy/Cim10Hierarchy.tsx | 6 +- .../CriteriaRightPanel/Cim10Form/index.tsx | 52 +- .../CriteriaForm/components/index.tsx | 133 ++ .../CriteriaForm/criteriaform.stories.tsx | 196 ++ .../CriteriaRightPanel/CriteriaForm/index.tsx | 112 + .../CriteriaForm/mappers/buildMappers.ts | 253 +++ .../mappers/chipDisplayMapper.tsx | 302 +++ .../CriteriaForm/mappers/index.ts | 317 +++ .../CriteriaForm/mappers/renderers.tsx | 287 +++ .../Form/styles.ts => CriteriaForm/style.ts} | 0 .../CriteriaRightPanel/CriteriaForm/types.ts | 344 +++ .../CriteriaRightPanel/CriteriaRightPanel.tsx | 52 +- .../DemographicForm/index.tsx | 242 --- .../DemographicForm/styles.ts | 51 - .../DocumentsForm/DocumentsForm.tsx | 328 --- .../DocumentsForm/styles.ts | 59 - .../EncounterForm/index.tsx | 434 ---- .../EncounterForm/styles.ts | 49 - .../GHM/components/Form/GhmForm.tsx | 217 -- .../GHM/components/Form/styles.ts | 53 - .../GHM/components/Hierarchy/GhmHierarchy.tsx | 6 +- .../CriteriaRightPanel/GHM/index.tsx | 52 +- .../CriteriaRightPanel/HospitForm/index.tsx | 925 --------- .../CriteriaRightPanel/HospitForm/styles.ts | 9 - .../CriteriaRightPanel/IPPForm/IPPForm.tsx | 147 -- .../CriteriaRightPanel/IPPForm/styles.ts | 51 - .../CriteriaRightPanel/ImagingForm/index.tsx | 422 ---- .../CriteriaRightPanel/ImagingForm/styles.ts | 16 - .../components/Form/MedicationForm.tsx | 287 --- .../MedicationForm/components/Form/styles.ts | 53 - .../Hierarchy/MedicationHierarchy.tsx | 6 +- .../MedicationForm/index.tsx | 59 +- .../CriteriaRightPanel/PregnantForm/index.tsx | 372 ---- .../CriteriaRightPanel/PregnantForm/styles.ts | 12 - .../RequestForm/RequestForm.tsx | 2 +- .../CriteriaRightPanel/forms/BiologyForm.ts | 165 ++ .../CriteriaRightPanel/forms/CCAMForm.ts | 168 ++ .../CriteriaRightPanel/forms/Cim10Form.ts | 180 ++ .../forms/DemographicForm.ts | 151 ++ .../CriteriaRightPanel/forms/DocumentsForm.ts | 197 ++ .../CriteriaRightPanel/forms/EncounterForm.ts | 263 +++ .../CriteriaRightPanel/forms/GHMForm.tsx | 146 ++ .../CriteriaRightPanel/forms/HospitForm.ts | 769 +++++++ .../CriteriaRightPanel/forms/IPPForm.ts | 43 + .../CriteriaRightPanel/forms/ImagingForm.ts | 313 +++ .../forms/MedicationForm.ts | 246 +++ .../CriteriaRightPanel/forms/PregnancyForm.ts | 305 +++ src/components/CreationCohort/Requeteur.tsx | 25 +- .../Filters/DatesRangeFilter/index.tsx | 23 +- .../Filters/DocStatusFilter/index.tsx | 2 +- src/components/ui/CriteriaLayout/index.tsx | 10 +- .../ui/Inputs/CalendarRange/index.tsx | 8 +- .../index.tsx | 57 +- src/components/ui/Inputs/Occurences/index.tsx | 140 +- .../ui/Inputs/OccurencesWithFloats/index.tsx | 86 - src/config.tsx | 50 +- src/state/cohortCreation.ts | 2 +- src/state/criteria.ts | 6 +- src/state/valueSets.ts | 89 +- src/types.ts | 29 +- src/types/requestCriterias.ts | 371 +--- src/types/searchCriterias.ts | 3 + src/utils/cohortCreation.ts | 1838 ++--------------- src/utils/displayValueSetSystem.ts | 12 - src/utils/mappers.ts | 384 ---- src/utils/pmsi.ts | 16 +- src/utils/requestCriterias.tsx | 955 --------- 97 files changed, 7564 insertions(+), 9326 deletions(-) create mode 100644 .storybook/main.ts create mode 100644 .storybook/preview.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/AdvancedInputs/AdvancedInputs.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Form/BiologyForm.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/components/Form/CCAMForm.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/components/Form/styles.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/components/Form/Cim10Form.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/components/Form/styles.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/components/index.tsx create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/criteriaform.stories.tsx create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/index.tsx create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/buildMappers.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/chipDisplayMapper.tsx create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/index.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/renderers.tsx rename src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/{BiologyForm/components/Form/styles.ts => CriteriaForm/style.ts} (100%) create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DemographicForm/index.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DemographicForm/styles.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DocumentsForm/DocumentsForm.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DocumentsForm/styles.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/EncounterForm/index.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/EncounterForm/styles.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/GhmForm.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/styles.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/index.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/styles.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/IPPForm/IPPForm.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/IPPForm/styles.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/index.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/styles.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Form/MedicationForm.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Form/styles.ts delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/PregnantForm/index.tsx delete mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/PregnantForm/styles.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/BiologyForm.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/CCAMForm.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/Cim10Form.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DemographicForm.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DocumentsForm.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/EncounterForm.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/GHMForm.tsx create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/HospitForm.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/IPPForm.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/ImagingForm.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/MedicationForm.ts create mode 100644 src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/PregnancyForm.ts rename src/components/ui/Inputs/{UidTextfield => CheckedTextfield}/index.tsx (51%) delete mode 100644 src/components/ui/Inputs/OccurencesWithFloats/index.tsx delete mode 100644 src/utils/displayValueSetSystem.ts delete mode 100644 src/utils/mappers.ts delete mode 100644 src/utils/requestCriterias.tsx diff --git a/.gitignore b/.gitignore index 349500c11..114f51af4 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ yarn-error.log* .env* !.env.example .mp4 -public/config.dev.json \ No newline at end of file +public/config.dev.json +*storybook.log \ No newline at end of file diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 000000000..33dd3e82f --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,40 @@ +import type { StorybookConfig } from '@storybook/react-vite' + +const config: StorybookConfig = { + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: [ + '@storybook/addon-onboarding', + '@storybook/addon-links', + '@storybook/addon-essentials', + '@chromatic-com/storybook', + '@storybook/addon-interactions' + ], + framework: { + name: '@storybook/react-vite', + options: {} + }, + docs: { + autodocs: 'tag', + }, + typescript: { + reactDocgen: 'react-docgen-typescript', + check: false, + skipCompiler: false, + reactDocgenTypescriptOptions: { + shouldExtractLiteralValuesFromEnum: true, + propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true), + }, + }, + async viteFinal(config) { + // Merge custom configuration into the default config + const { mergeConfig } = await import('vite'); + + return mergeConfig(config, { + // Add dependencies to pre-optimization + optimizeDeps: { + include: ['storybook-dark-mode'], + }, + }); + }, +} +export default config diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 000000000..cf8244a2b --- /dev/null +++ b/.storybook/preview.ts @@ -0,0 +1,14 @@ +import type { Preview } from '@storybook/react' + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i + } + } + } +} + +export default preview diff --git a/package-lock.json b/package-lock.json index af69a40f0..51a07bf7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,15 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@chromatic-com/storybook": "^1.9.0", + "@storybook/addon-essentials": "^8.3.5", + "@storybook/addon-interactions": "^8.3.5", + "@storybook/addon-links": "^8.3.5", + "@storybook/addon-onboarding": "^8.3.5", + "@storybook/blocks": "^8.3.5", + "@storybook/react": "^8.3.5", + "@storybook/react-vite": "^8.3.5", + "@storybook/test": "^8.3.5", "@testing-library/dom": "^8.20.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", @@ -91,8 +100,11 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-storybook": "^0.9.0", "normalize-url": "^7.0.3", "prettier": "^2.8.7", + "react-docgen-typescript": "^2.2.2", + "storybook": "^8.3.5", "typescript": "^4.9.5", "vite": "^5.4.6", "vite-plugin-static-copy": "1.0.0", @@ -2384,12 +2396,71 @@ "node": ">=6.9.0" } }, + "node_modules/@base2/pretty-print-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz", + "integrity": "sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==", + "dev": true + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "license": "MIT" }, + "node_modules/@chromatic-com/storybook": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-1.9.0.tgz", + "integrity": "sha512-vYQ+TcfktEE3GHnLZXHCzXF/sN9dw+KivH8a5cmPyd9YtQs7fZtHrEgsIjWpYycXiweKMo1Lm1RZsjxk8DH3rA==", + "dev": true, + "dependencies": { + "chromatic": "^11.4.0", + "filesize": "^10.0.12", + "jsonfile": "^6.1.0", + "react-confetti": "^6.1.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=16.0.0", + "yarn": ">=1.22.18" + } + }, + "node_modules/@chromatic-com/storybook/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@chromatic-com/storybook/node_modules/filesize": { + "version": "10.1.6", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz", + "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==", + "dev": true, + "engines": { + "node": ">= 10.4.0" + } + }, + "node_modules/@chromatic-com/storybook/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@csstools/normalize.css": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz", @@ -5149,6 +5220,39 @@ "node": ">=8" } }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.3.0.tgz", + "integrity": "sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==", + "dev": true, + "dependencies": { + "glob": "^7.2.0", + "glob-promise": "^4.2.0", + "magic-string": "^0.27.0", + "react-docgen-typescript": "^2.2.2" + }, + "peerDependencies": { + "typescript": ">= 4.3.x", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -5279,6 +5383,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@mdx-js/react": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", + "integrity": "sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==", + "dev": true, + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, "node_modules/@mui/base": { "version": "5.0.0-beta.40", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", @@ -6068,43 +6189,917 @@ "win32" ] }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", - "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", - "license": "MIT", - "peer": true + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", + "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", + "license": "MIT", + "peer": true + }, + "node_modules/@servie/events": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@servie/events/-/events-1.0.0.tgz", + "integrity": "sha512-sBSO19KzdrJCM3gdx6eIxV8M9Gxfgg6iDQmH5TIAGaUu+X9VDdsINXJOnoiZ1Kx3TrHdH4bt5UVglkjsEGBcvw==", + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@storybook/addon-actions": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.3.5.tgz", + "integrity": "sha512-t8D5oo+4XfD+F8091wLa2y/CDd/W2lExCeol5Vm1tp5saO+u6f2/d7iykLhTowWV84Uohi3D073uFeyTAlGebg==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "@types/uuid": "^9.0.1", + "dequal": "^2.0.2", + "polished": "^4.2.2", + "uuid": "^9.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-actions/node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, + "node_modules/@storybook/addon-backgrounds": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.3.5.tgz", + "integrity": "sha512-IQGjDujuw8+iSqKREdkL8I5E/5CAHZbfOWd4A75PQK2D6qZ0fu/xRwTOQOH4jP6xn/abvfACOdL6A0d5bU90ag==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "memoizerific": "^1.11.3", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-controls": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.3.5.tgz", + "integrity": "sha512-2eCVobUUvY1Rq7sp1U8Mx8t44VXwvi0E+hqyrsqOx5TTSC/FUQ+hNAX6GSYUcFIyQQ1ORpKNlUjAAdjxBv1ZHQ==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-docs": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.3.5.tgz", + "integrity": "sha512-MOVfo1bY8kXTzbvmWnx3UuSO4WNykFz7Edvb3mxltNyuW7UDRZGuIuSe32ddT/EtLJfurrC9Ja3yBy4KBUGnMA==", + "dev": true, + "dependencies": { + "@mdx-js/react": "^3.0.0", + "@storybook/blocks": "8.3.5", + "@storybook/csf-plugin": "8.3.5", + "@storybook/global": "^5.0.0", + "@storybook/react-dom-shim": "8.3.5", + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "fs-extra": "^11.1.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "rehype-external-links": "^3.0.0", + "rehype-slug": "^6.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-docs/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@storybook/addon-essentials": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.3.5.tgz", + "integrity": "sha512-hXTtPuN4/IsXjUrkMPAuz1qKAl8DovdXpjQgjQs7jSAVx3kc4BZaGqJ3gaVenKtO8uDchmA92BoQygpkc8eWhw==", + "dev": true, + "dependencies": { + "@storybook/addon-actions": "8.3.5", + "@storybook/addon-backgrounds": "8.3.5", + "@storybook/addon-controls": "8.3.5", + "@storybook/addon-docs": "8.3.5", + "@storybook/addon-highlight": "8.3.5", + "@storybook/addon-measure": "8.3.5", + "@storybook/addon-outline": "8.3.5", + "@storybook/addon-toolbars": "8.3.5", + "@storybook/addon-viewport": "8.3.5", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-highlight": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.3.5.tgz", + "integrity": "sha512-ku0epul9aReCR3Gv/emwYnsqg3vgux5OmYMjoDcJC7s+LyfweSzLV/f5t9gSHazikJElh5TehtVkWbC4QfbGSw==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-interactions": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-8.3.5.tgz", + "integrity": "sha512-GtTy/A+mG7vDOahQr2avT4dpWtCRiFDSYcWyuQOZm10y8VDDw157HQM+FuhxjV9Owrrohy9F24oBUwRG8H3b5A==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/instrumenter": "8.3.5", + "@storybook/test": "8.3.5", + "polished": "^4.2.2", + "ts-dedent": "^2.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-links": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.3.5.tgz", + "integrity": "sha512-giRCpn6cfJMYPnVJkojoQDO5ae6098fgY9YgAhwaJej/9dufNcioFdbiyfK1vyzbG6TGeTmJ9ncWCXgWRtzxPQ==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.1.11", + "@storybook/global": "^5.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.3.5" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-measure": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.3.5.tgz", + "integrity": "sha512-6GVehgbHhFIFS69xSfRV+12VK0cnuIAtZdp1J3eUCc2ATrcigqVjTM6wzZz6kBuX6O3dcusr7Wg46KtNliqLqg==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "tiny-invariant": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-onboarding": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-onboarding/-/addon-onboarding-8.3.5.tgz", + "integrity": "sha512-QE/+6KEYO5tGziMdo+81oor0KNVnbPsfDpnhtClu+t1XC2F2nKQpDISujwLSYm9voEk1D/NxYWMbQ6eTDR/ViA==", + "dev": true, + "dependencies": { + "react-confetti": "^6.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-outline": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.3.5.tgz", + "integrity": "sha512-dwmK6GzjEnQP9Yo0VnBUQtJkXZlXdfjWyskZ/IlUVc+IFdeeCtIiMyA92oMfHo8eXt0k1g21ZqMaIn7ZltOuHw==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-toolbars": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.3.5.tgz", + "integrity": "sha512-Ml2gc9q8WbteDvmuAZGgBxt5SqWMXzuTkMjlsA8EB53hlkN1w9esX4s8YtBeNqC3HKoUzcdq8uexSBqU8fDbSA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/addon-viewport": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.3.5.tgz", + "integrity": "sha512-FSWydoPiVWFXEittG7O1YgvuaqoU9Vb+qoq9XfP/hvQHHMDcMZvC40JaV8AnJeTXaM7ngIjcn9XDEfGbFfOzXw==", + "dev": true, + "dependencies": { + "memoizerific": "^1.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/blocks": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.3.5.tgz", + "integrity": "sha512-8cHTdTywolTHlgwN8I7YH7saWAIjGzV617AwjhJ95AKlC0VtpO1gAFcAgCqr4DU9eMc+LZuvbnaU/RSvA5eCCQ==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.1.11", + "@storybook/global": "^5.0.0", + "@storybook/icons": "^1.2.10", + "@types/lodash": "^4.14.167", + "color-convert": "^2.0.1", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "markdown-to-jsx": "^7.4.5", + "memoizerific": "^1.11.3", + "polished": "^4.2.2", + "react-colorful": "^5.1.2", + "telejson": "^7.2.0", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.3.5" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/blocks/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@storybook/blocks/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@storybook/builder-vite": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-8.3.5.tgz", + "integrity": "sha512-paGX8tEmAeAKFU5Cnwkq3RAi3LFCnmjAxMJikT09jUi6jDpNa0VzH8jbLxKdjsPMAsz0Wv3mrLvL2b8hyxLWAw==", + "dev": true, + "dependencies": { + "@storybook/csf-plugin": "8.3.5", + "@types/find-cache-dir": "^3.2.1", + "browser-assert": "^1.2.1", + "es-module-lexer": "^1.5.0", + "express": "^4.19.2", + "find-cache-dir": "^3.0.0", + "fs-extra": "^11.1.0", + "magic-string": "^0.30.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@preact/preset-vite": "*", + "storybook": "^8.3.5", + "typescript": ">= 4.3.x", + "vite": "^4.0.0 || ^5.0.0", + "vite-plugin-glimmerx": "*" + }, + "peerDependenciesMeta": { + "@preact/preset-vite": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vite-plugin-glimmerx": { + "optional": true + } + } + }, + "node_modules/@storybook/builder-vite/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@storybook/components": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.3.5.tgz", + "integrity": "sha512-Rq28YogakD3FO4F8KwAtGpo1g3t4V/gfCLqTQ8B6oQUFoxLqegkWk/DlwCzvoJndXuQJfdSyM6+r1JcA4Nql5A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/core": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.3.5.tgz", + "integrity": "sha512-GOGfTvdioNa/n+Huwg4u/dsyYyBcM+gEcdxi3B7i5x4yJ3I912KoVshumQAOF2myKSRdI8h8aGWdx7nnjd0+5Q==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.1.11", + "@types/express": "^4.17.21", + "better-opn": "^3.0.2", + "browser-assert": "^1.2.1", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0", + "esbuild-register": "^3.5.0", + "express": "^4.19.2", + "jsdoc-type-pratt-parser": "^4.0.0", + "process": "^0.11.10", + "recast": "^0.23.5", + "semver": "^7.6.2", + "util": "^0.12.5", + "ws": "^8.2.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/core/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@storybook/csf": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.11.tgz", + "integrity": "sha512-dHYFQH3mA+EtnCkHXzicbLgsvzYjcDJ1JWsogbItZogkPHgSJM/Wr71uMkcvw8v9mmCyP4NpXJuu6bPoVsOnzg==", + "dev": true, + "dependencies": { + "type-fest": "^2.19.0" + } + }, + "node_modules/@storybook/csf-plugin": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.3.5.tgz", + "integrity": "sha512-ODVqNXwJt90hG7QW8I9w/XUyOGlr0l7XltmIJgXwB/2cYDvaGu3JV5Ybg7O0fxPV8uXk7JlRuUD8ZYv5Low6pA==", + "dev": true, + "dependencies": { + "unplugin": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/csf/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/global": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", + "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", + "dev": true + }, + "node_modules/@storybook/icons": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.2.12.tgz", + "integrity": "sha512-UxgyK5W3/UV4VrI3dl6ajGfHM4aOqMAkFLWe2KibeQudLf6NJpDrDMSHwZj+3iKC4jFU7dkKbbtH2h/al4sW3Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/instrumenter": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.3.5.tgz", + "integrity": "sha512-NLDXai5y2t1ITgHVK9chyL0rMFZbICCOGcnTbyWhkLbiEWZKPJ8FuB8+g+Ba6zwtCve1A1Cnb4O2LOWy7TgWQw==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "@vitest/utils": "^2.0.5", + "util": "^0.12.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/manager-api": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.3.5.tgz", + "integrity": "sha512-fEQoKKi7h7pzh2z9RfuzatJxubrsfL/CB99fNXQ0wshMSY/7O4ckd18pK4fzG9ErnCtLAO9qsim4N/4eQC+/8Q==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/preview-api": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.3.5.tgz", + "integrity": "sha512-VPqpudE8pmjTLvdNJoW/2//nqElDgUOmIn3QxbbCmdZTHDg5tFtxuqwdlNfArF0TxvTSBDIulXt/Q6K56TAfTg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/react": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.3.5.tgz", + "integrity": "sha512-kuBPe/wBin10SWr4EWPKxiTRGQ4RD2etGEVWVQLqVpOuJp/J2hVvXQHtCfZXU4TZT5x4PBbPRswbr58+XlF+kQ==", + "dev": true, + "dependencies": { + "@storybook/components": "^8.3.5", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "^8.3.5", + "@storybook/preview-api": "^8.3.5", + "@storybook/react-dom-shim": "8.3.5", + "@storybook/theming": "^8.3.5", + "@types/escodegen": "^0.0.6", + "@types/estree": "^0.0.51", + "@types/node": "^22.0.0", + "acorn": "^7.4.1", + "acorn-jsx": "^5.3.1", + "acorn-walk": "^7.2.0", + "escodegen": "^2.1.0", + "html-tags": "^3.1.0", + "prop-types": "^15.7.2", + "react-element-to-jsx-string": "^15.0.0", + "semver": "^7.3.7", + "ts-dedent": "^2.0.0", + "type-fest": "~2.19", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@storybook/test": "8.3.5", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.3.5", + "typescript": ">= 4.2.x" + }, + "peerDependenciesMeta": { + "@storybook/test": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@storybook/react-dom-shim": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.3.5.tgz", + "integrity": "sha512-Hf0UitJ/K0C7ajooooUK/PxOR4ihUWqsC7iCV1Gqth8U37dTeLMbaEO4PBwu0VQ+Ufg0N8BJLWfg7o6G4hrODw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/react-vite": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-8.3.5.tgz", + "integrity": "sha512-1pnN1JB7GrHUoTVn8VGkS240VNGhWkZBOMaaaRQnkgY1dCrFxAQv4YKFVuC250+rQzgp8X33J/pDAukgwzWYFQ==", + "dev": true, + "dependencies": { + "@joshwooding/vite-plugin-react-docgen-typescript": "0.3.0", + "@rollup/pluginutils": "^5.0.2", + "@storybook/builder-vite": "8.3.5", + "@storybook/react": "8.3.5", + "find-up": "^5.0.0", + "magic-string": "^0.30.0", + "react-docgen": "^7.0.0", + "resolve": "^1.22.8", + "tsconfig-paths": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.3.5", + "vite": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/@storybook/react-vite/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@storybook/react-vite/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@storybook/react/node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "node_modules/@storybook/react/node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@storybook/react/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@storybook/react/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/react/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/@storybook/test": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.3.5.tgz", + "integrity": "sha512-1BXWsUGWk9FiKKelZZ55FDJdeoL8uRBHbjTYBRM2xJLhdNSvGzI4Tb3bkmxPpGn72Ua6AyldhlTxr2BpUFKOHA==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.1.11", + "@storybook/global": "^5.0.0", + "@storybook/instrumenter": "8.3.5", + "@testing-library/dom": "10.4.0", + "@testing-library/jest-dom": "6.5.0", + "@testing-library/user-event": "14.5.2", + "@vitest/expect": "2.0.5", + "@vitest/spy": "2.0.5", + "util": "^0.12.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" + } + }, + "node_modules/@storybook/test/node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@storybook/test/node_modules/@testing-library/jest-dom": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz", + "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@storybook/test/node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/test/node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true + }, + "node_modules/@storybook/test/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@storybook/test/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@storybook/test/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@storybook/test/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "node_modules/@servie/events": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@servie/events/-/events-1.0.0.tgz", - "integrity": "sha512-sBSO19KzdrJCM3gdx6eIxV8M9Gxfgg6iDQmH5TIAGaUu+X9VDdsINXJOnoiZ1Kx3TrHdH4bt5UVglkjsEGBcvw==", - "license": "MIT" + "node_modules/@storybook/test/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - "node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "license": "MIT" + "node_modules/@storybook/test/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "license": "BSD-3-Clause", - "peer": true, + "node_modules/@storybook/test/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "type-detect": "4.0.8" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" + "node_modules/@storybook/theming": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.3.5.tgz", + "integrity": "sha512-9HmDDyC691oqfg4RziIM9ElsS2HITaxmH7n/yeUPtuirkPdAQzqOzhvH/Sa0qOhifzs8VjR+Gd/a/ZQ+S38r7w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.3.5" } }, "node_modules/@surma/rollup-plugin-off-main-thread": { @@ -6889,7 +7884,6 @@ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "license": "MIT", - "peer": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -6910,7 +7904,6 @@ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -7227,6 +8220,12 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/doctrine": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", + "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", + "dev": true + }, "node_modules/@types/domhandler": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/@types/domhandler/-/domhandler-2.4.5.tgz", @@ -7254,6 +8253,12 @@ "@types/domhandler": "^2.4.0" } }, + "node_modules/@types/escodegen": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.6.tgz", + "integrity": "sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==", + "dev": true + }, "node_modules/@types/eslint": { "version": "8.56.11", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz", @@ -7275,7 +8280,6 @@ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -7288,7 +8292,6 @@ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -7303,6 +8306,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/find-cache-dir": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz", + "integrity": "sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==", + "dev": true + }, "node_modules/@types/geojson": { "version": "7946.0.14", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", @@ -7310,6 +8319,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -7320,6 +8339,15 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/history": { "version": "4.7.11", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", @@ -7361,8 +8389,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/http-proxy": { "version": "1.17.15", @@ -7467,12 +8494,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT", - "peer": true + "license": "MIT" + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true }, "node_modules/@types/node": { "version": "18.19.45", @@ -7523,8 +8561,7 @@ "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/ramda": { "version": "0.30.2", @@ -7539,8 +8576,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.4", @@ -7695,7 +8731,6 @@ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "license": "MIT", - "peer": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -7716,7 +8751,6 @@ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "license": "MIT", - "peer": true, "dependencies": { "@types/http-errors": "*", "@types/node": "*", @@ -7761,6 +8795,12 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -8374,7 +9414,6 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", - "peer": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -8449,7 +9488,6 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.4.0" } @@ -8714,8 +9752,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/array-includes": { "version": "3.1.8", @@ -8899,6 +9936,18 @@ "node": ">=12" } }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -9438,6 +10487,18 @@ "license": "MIT", "peer": true }, + "node_modules/better-opn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", + "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==", + "dev": true, + "dependencies": { + "open": "^8.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/bfj": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.1.0.tgz", @@ -9497,7 +10558,6 @@ "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "peer": true, "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -9521,7 +10581,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "peer": true, "engines": { "node": ">= 0.8" } @@ -9530,7 +10589,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -9539,7 +10597,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -9550,8 +10607,7 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/bonjour-service": { "version": "1.2.1", @@ -9608,6 +10664,12 @@ "unload": "2.2.0" } }, + "node_modules/browser-assert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", + "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==", + "dev": true + }, "node_modules/browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -9960,6 +11022,29 @@ "node": ">=10" } }, + "node_modules/chromatic": { + "version": "11.12.5", + "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-11.12.5.tgz", + "integrity": "sha512-5z+BXQy3TMyXIzCdCDO9Psc8aMs9kIrCFHhMgYbwA6dTXxAL0oUjHZbICn5h4Ay/fM9cZQPaCH9T7a3myPA8Sw==", + "dev": true, + "bin": { + "chroma": "dist/bin.js", + "chromatic": "dist/bin.js", + "chromatic-cli": "dist/bin.js" + }, + "peerDependencies": { + "@chromatic-com/cypress": "^0.*.* || ^1.0.0", + "@chromatic-com/playwright": "^0.*.* || ^1.0.0" + }, + "peerDependenciesMeta": { + "@chromatic-com/cypress": { + "optional": true + }, + "@chromatic-com/playwright": { + "optional": true + } + } + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -10156,8 +11241,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/compressible": { "version": "2.0.18", @@ -10250,7 +11334,6 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -10262,7 +11345,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "peer": true, "engines": { "node": ">= 0.6" } @@ -10278,7 +11360,6 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -10287,8 +11368,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/core-js": { "version": "3.38.1", @@ -11510,7 +12590,6 @@ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -11561,7 +12640,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "peer": true, "engines": { "node": ">= 0.8" } @@ -11579,7 +12657,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "peer": true, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -11911,8 +12988,7 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "peer": true + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { "version": "3.1.10", @@ -11969,7 +13045,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "peer": true, "engines": { "node": ">= 0.8" } @@ -12156,8 +13231,7 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.0.0", @@ -12250,6 +13324,18 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -12263,8 +13349,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -12283,7 +13368,6 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -12306,7 +13390,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", "optional": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12786,6 +13869,33 @@ "semver": "bin/semver.js" } }, + "node_modules/eslint-plugin-storybook": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.9.0.tgz", + "integrity": "sha512-qOT/2vQBo0VqrG/BhZv8IdSsKQiyzJw+2Wqq+WFCiblI/PfxLSrGkF/buiXF+HumwfsCyBdaC94UhqhmYFmAvA==", + "dev": true, + "dependencies": { + "@storybook/csf": "^0.0.1", + "@typescript-eslint/utils": "^5.62.0", + "requireindex": "^1.2.0", + "ts-dedent": "^2.2.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "eslint": ">=6" + } + }, + "node_modules/eslint-plugin-storybook/node_modules/@storybook/csf": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.1.tgz", + "integrity": "sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/eslint-plugin-testing-library": { "version": "5.11.1", "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz", @@ -13064,7 +14174,6 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "license": "BSD-2-Clause", - "peer": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -13126,7 +14235,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "peer": true, "engines": { "node": ">= 0.6" } @@ -13202,7 +14310,6 @@ "version": "4.21.0", "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -13245,7 +14352,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -13254,8 +14360,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -13467,7 +14572,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -13485,7 +14589,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -13493,15 +14596,13 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "license": "MIT", - "peer": true, "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -13519,7 +14620,6 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "license": "MIT", - "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -13535,7 +14635,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -13852,7 +14951,6 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -13875,7 +14973,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "peer": true, "engines": { "node": ">= 0.6" } @@ -14108,6 +15205,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "dev": true + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -14141,6 +15244,25 @@ "node": ">=10.13.0" } }, + "node_modules/glob-promise": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz", + "integrity": "sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/ahmadnassri" + }, + "peerDependencies": { + "glob": "^7.1.6" + } + }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -14392,6 +15514,45 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -14627,6 +15788,18 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -14717,7 +15890,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "peer": true, "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -15022,6 +16194,18 @@ "node": ">= 10" } }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -15177,7 +16361,6 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "license": "MIT", - "peer": true, "bin": { "is-docker": "cli.js" }, @@ -15342,6 +16525,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -15522,7 +16714,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "license": "MIT", - "peer": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -19439,6 +20630,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jsdom": { "version": "16.7.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", @@ -20001,6 +21201,24 @@ "tmpl": "1.0.5" } }, + "node_modules/map-or-similar": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", + "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", + "dev": true + }, + "node_modules/markdown-to-jsx": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.5.0.tgz", + "integrity": "sha512-RrBNcMHiFPcz/iqIj0n3wclzHXjwS7mzjBNWecKKVhNTIxQepIix6Il/wZCn2Cg5Y1ow2Qi84+eJrryFRWBEWw==", + "dev": true, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -20012,7 +21230,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "peer": true, "engines": { "node": ">= 0.6" } @@ -20030,11 +21247,19 @@ "node": ">= 4.0.0" } }, + "node_modules/memoizerific": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", + "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", + "dev": true, + "dependencies": { + "map-or-similar": "^1.5.0" + } + }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "peer": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -20076,7 +21301,6 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -20103,7 +21327,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "peer": true, "bin": { "mime": "cli.js" }, @@ -20372,7 +21595,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -20709,7 +21931,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -20757,7 +21978,6 @@ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "license": "MIT", - "peer": true, "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -20836,7 +22056,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -20900,7 +22119,6 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -20974,8 +22192,7 @@ "node_modules/path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", - "peer": true + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", @@ -21076,7 +22293,6 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "license": "MIT", - "peer": true, "dependencies": { "find-up": "^4.0.0" }, @@ -21089,7 +22305,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -21103,7 +22318,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -21116,7 +22330,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -21132,7 +22345,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -21219,6 +22431,18 @@ "node": ">=4" } }, + "node_modules/polished": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/popsicle": { "version": "12.1.2", "resolved": "https://registry.npmjs.org/popsicle/-/popsicle-12.1.2.tgz", @@ -22768,6 +23992,15 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "license": "MIT" }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -22821,7 +24054,6 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", - "peer": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -22835,7 +24067,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.10" } @@ -22877,7 +24108,6 @@ "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "peer": true, "dependencies": { "side-channel": "^1.0.6" }, @@ -22956,7 +24186,6 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -22965,7 +24194,6 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "peer": true, "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -22980,7 +24208,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "peer": true, "engines": { "node": ">= 0.8" } @@ -22989,7 +24216,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -23075,6 +24301,31 @@ "semver": "bin/semver" } }, + "node_modules/react-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "dev": true, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-confetti": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-6.1.0.tgz", + "integrity": "sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==", + "dev": true, + "dependencies": { + "tween-functions": "^1.2.0" + }, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "react": "^16.3.0 || ^17.0.1 || ^18.0.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -23197,6 +24448,57 @@ "node": ">=8" } }, + "node_modules/react-docgen": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-7.0.3.tgz", + "integrity": "sha512-i8aF1nyKInZnANZ4uZrH49qn1paRgBZ7wZiCNBMnenlPzEv0mRl+ShpTVEI6wZNl8sSc79xZkivtgLKQArcanQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.18.9", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9", + "@types/babel__core": "^7.18.0", + "@types/babel__traverse": "^7.18.0", + "@types/doctrine": "^0.0.9", + "@types/resolve": "^1.20.2", + "doctrine": "^3.0.0", + "resolve": "^1.22.1", + "strip-indent": "^4.0.0" + }, + "engines": { + "node": ">=16.14.0" + } + }, + "node_modules/react-docgen-typescript": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", + "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", + "dev": true, + "peerDependencies": { + "typescript": ">= 4.3.x" + } + }, + "node_modules/react-docgen/node_modules/@types/resolve": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", + "integrity": "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==", + "dev": true + }, + "node_modules/react-docgen/node_modules/strip-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", + "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -23210,6 +24512,27 @@ "react": "^18.3.1" } }, + "node_modules/react-element-to-jsx-string": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz", + "integrity": "sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==", + "dev": true, + "dependencies": { + "@base2/pretty-print-object": "1.0.1", + "is-plain-object": "5.0.0", + "react-is": "18.1.0" + }, + "peerDependencies": { + "react": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0", + "react-dom": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/react-element-to-jsx-string/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, "node_modules/react-error-overlay": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", @@ -23520,6 +24843,31 @@ "node": ">=8.10.0" } }, + "node_modules/recast": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", + "integrity": "sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==", + "dev": true, + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/recursive-readdir": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", @@ -23775,6 +25123,41 @@ "jsesc": "bin/jsesc" } }, + "node_modules/rehype-external-links": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rehype-external-links/-/rehype-external-links-3.0.0.tgz", + "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-is-element": "^3.0.0", + "is-absolute-url": "^4.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -23908,6 +25291,15 @@ "node": ">=0.10.0" } }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -24360,7 +25752,6 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "peer": true, "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -24384,7 +25775,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -24392,14 +25782,12 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "peer": true + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "peer": true, "engines": { "node": ">= 0.8" } @@ -24407,8 +25795,7 @@ "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "peer": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { "version": "6.0.2", @@ -24510,7 +25897,6 @@ "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "peer": true, "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -24574,8 +25960,7 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "peer": true + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -24812,6 +26197,16 @@ "license": "MIT", "peer": true }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -25006,7 +26401,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "peer": true, "engines": { "node": ">= 0.8" } @@ -25030,6 +26424,24 @@ "node": ">= 0.4" } }, + "node_modules/storybook": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.3.5.tgz", + "integrity": "sha512-hYQVtP2l+3kO8oKDn4fjXXQYxgTRsj/LaV6lUMJH0zt+OhVmDXKJLxmdUP4ieTm0T8wEbSYosFavgPcQZlxRfw==", + "dev": true, + "dependencies": { + "@storybook/core": "8.3.5" + }, + "bin": { + "getstorybook": "bin/index.cjs", + "sb": "bin/index.cjs", + "storybook": "bin/index.cjs" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -25724,6 +27136,15 @@ "license": "ISC", "optional": true }, + "node_modules/telejson": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/telejson/-/telejson-7.2.0.tgz", + "integrity": "sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==", + "dev": true, + "dependencies": { + "memoizerific": "^1.11.3" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -26068,7 +27489,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "peer": true, "engines": { "node": ">=0.6" } @@ -26121,6 +27541,15 @@ "license": "MIT", "peer": true }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true, + "engines": { + "node": ">=6.10" + } + }, "node_modules/ts-expect": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-expect/-/ts-expect-1.3.0.tgz", @@ -26225,6 +27654,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, + "node_modules/tween-functions": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz", + "integrity": "sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -26264,7 +27699,6 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "peer": true, "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -26463,6 +27897,48 @@ "node": ">=8" } }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -26486,11 +27962,31 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "peer": true, "engines": { "node": ">= 0.8" } }, + "node_modules/unplugin": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.1.tgz", + "integrity": "sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w==", + "dev": true, + "dependencies": { + "acorn": "^8.12.1", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "webpack-sources": "^3" + }, + "peerDependenciesMeta": { + "webpack-sources": { + "optional": true + } + } + }, "node_modules/unquote": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", @@ -26567,6 +28063,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -26601,7 +28110,6 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4.0" } @@ -26649,7 +28157,6 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -27629,6 +29136,12 @@ "node": ">=10.13.0" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true + }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", diff --git a/package.json b/package.json index ba3f6d762..c58f4db20 100644 --- a/package.json +++ b/package.json @@ -60,9 +60,11 @@ "build": "vite build", "serve": "vite preview", "lint": "eslint --ext .jsx,.js,.ts,.tsx src/", - "test": "vitest", + "test": "vitest", "coverage": "vitest run --coverage", - "ui": "vitest --ui" + "ui": "vitest --ui", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" }, "browserslist": { "production": [ @@ -77,6 +79,15 @@ ] }, "devDependencies": { + "@chromatic-com/storybook": "^1.9.0", + "@storybook/addon-essentials": "^8.3.5", + "@storybook/addon-interactions": "^8.3.5", + "@storybook/addon-links": "^8.3.5", + "@storybook/addon-onboarding": "^8.3.5", + "@storybook/blocks": "^8.3.5", + "@storybook/react": "^8.3.5", + "@storybook/react-vite": "^8.3.5", + "@storybook/test": "^8.3.5", "@testing-library/dom": "^8.20.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", @@ -114,8 +125,11 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-storybook": "^0.9.0", "normalize-url": "^7.0.3", "prettier": "^2.8.7", + "react-docgen-typescript": "^2.2.2", + "storybook": "^8.3.5", "typescript": "^4.9.5", "vite": "^5.4.6", "vite-plugin-static-copy": "1.0.0", @@ -126,5 +140,10 @@ }, "jest": { "clearMocks": true + }, + "eslintConfig": { + "extends": [ + "plugin:storybook/recommended" + ] } } diff --git a/src/__tests__/cohortCreation/cohortCreation.test.ts b/src/__tests__/cohortCreation/cohortCreation.test.ts index 5e3908d16..d0e8fe641 100644 --- a/src/__tests__/cohortCreation/cohortCreation.test.ts +++ b/src/__tests__/cohortCreation/cohortCreation.test.ts @@ -1,15 +1,24 @@ import { - CcamDataType, - Cim10DataType, - DemographicDataType, - DocumentDataType, - EncounterDataType, - GhmDataType, - ImagingDataType, - MedicationDataType, - ObservationDataType, SelectedCriteriaType } from 'types/requestCriterias' +import { + CcamDataType +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/CCAMForm' +import { + Cim10DataType +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/Cim10Form' +import { + DemographicDataType +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DemographicForm' +import { + DocumentDataType +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DocumentsForm' +import { + EncounterDataType +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/EncounterForm' +import { + GhmDataType +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/GHMForm' import { procedurePeudonimizedCriteria, ippNominativeCriteria, @@ -40,22 +49,11 @@ import { import { completeEncounterCriteria, defaultEncounterCriteira } from '__tests__/data/cohortCreation/encounterCriteria' import { checkNominativeCriteria, - buildPatientFilter, - buildEncounterFilter, - buildDocumentFilter, - buildConditionFilter, - buildProcedureFilter, - buildClaimFilter, - buildMedicationFilter, - buildObservationFilter, - buildImagingFilter, - buildPregnancyFilter, - buildHospitFilter + constructFhirFilter, } from 'utils/cohortCreation' import { completeDocumentCriteria, defaultDocumentCriteria } from '__tests__/data/cohortCreation/documentCriteria' import { completeConditionCriteria, defaultConditionCriteria } from '__tests__/data/cohortCreation/conditionCriteria' import { completeProcedureCriteria, defaultProcedureCriteria } from '__tests__/data/cohortCreation/procedureCriteria' -import { completeClaimCriteria, defaultClaimCriteria } from '__tests__/data/cohortCreation/claimCriteria' import { completeMedicationAdministrationCriteria, completeMedicationPrescriptionCriteria, @@ -66,6 +64,26 @@ import { defaultObservationCriteria } from '__tests__/data/cohortCreation/observationCriteria' import { completeImagingCriteria, defaultImagingCriteria } from '__tests__/data/cohortCreation/imagingCriteria' +import { + MedicationDataType} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/MedicationForm' +import criteriaList, { getAllCriteriaItems } from 'components/CreationCohort/DataList_Criteria' +import { CriteriaItemType } from 'types' +import { ImagingDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/ImagingForm' +import { ObservationDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/BiologyForm' +import { completeClaimCriteria, defaultClaimCriteria } from '__tests__/data/cohortCreation/claimCriteria' + +let allCriterias: CriteriaItemType[] | undefined = undefined + +const getCriteriaList = () => { + if (!allCriterias) { + allCriterias = getAllCriteriaItems(criteriaList()) + } + return allCriterias +} + +const buildFilter = (criteria: SelectedCriteriaType, deidentified?: boolean) => { + return constructFhirFilter(criteria, deidentified ?? false, getCriteriaList()).split("&") +} describe('test of checkNominativeCriteria', () => { it('should return false if selectedCriteria is an empty array', () => { @@ -134,32 +152,32 @@ describe('test of buildPatientFilter', () => { it('should return default build Patient', () => { const selectedCriteria: DemographicDataType = defaultPatientCriteria const result = ['active=true', '', '', '', '', '', '', '', ''] - expect(buildPatientFilter(selectedCriteria, false)).toEqual(result) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return gender female build Patient', () => { const selectedCriteria: DemographicDataType = patientGenderFemaleCriteria const result = ['active=true', 'gender=f', '', '', '', '', '', '', ''] - expect(buildPatientFilter(selectedCriteria, false)).toEqual(result) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return genders build Patient', () => { const selectedCriteria: DemographicDataType = patientGendersCriteria const result = ['active=true', 'gender=f,m', '', '', '', '', '', '', ''] - expect(buildPatientFilter(selectedCriteria, false)).toEqual(result) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return deceased vitalStatus build Patient', () => { const selectedCriteria: DemographicDataType = patientDeceasedVitalStatusCriteria const result = ['active=true', '', 'deceased=true', '', '', '', '', '', ''] - expect(buildPatientFilter(selectedCriteria, false)).toEqual(result) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return vitalStatus build Patient', () => { const selectedCriteria: DemographicDataType = patientVitalStatusCriteria const result = ['active=true', '', 'deceased=true,false', '', '', '', '', '', ''] - expect(buildPatientFilter(selectedCriteria, false)).toEqual(result) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return age build Patient', () => { const selectedCriteria: DemographicDataType = patientNominativeAgeCriteria const result = ['active=true', '', '', '', '', '', '', 'age-day=ge3082', 'age-day=le9360'] - expect(buildPatientFilter(selectedCriteria, false)).toEqual(result) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return birthdates build Patient', () => { const selectedCriteria: DemographicDataType = patientNominativeBirthDatesCriteria @@ -174,7 +192,7 @@ describe('test of buildPatientFilter', () => { '', '' ] - expect(buildPatientFilter(selectedCriteria, false)).toEqual(result) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return deathdates build Patient', () => { const selectedCriteria: DemographicDataType = patientNominativeDeathDatesCriteria @@ -189,7 +207,7 @@ describe('test of buildPatientFilter', () => { '', '' ] - expect(buildPatientFilter(selectedCriteria, false)).toEqual(result) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return complete build Patient', () => { const selectedCriteria: DemographicDataType = completePatientCriteria @@ -215,8 +233,8 @@ describe('test of buildPatientFilter', () => { 'age-day=ge3082', 'age-day=le9360' ] - expect(buildPatientFilter(selectedCriteria, false)).toEqual(result) - expect(buildPatientFilter(selectedCriteria, false)).not.toEqual(error) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) + expect(buildFilter(selectedCriteria, false)).not.toEqual(error) }) it('should return complete build Patient with no age', () => { const selectedCriteria: DemographicDataType = completePatientCriteria @@ -231,7 +249,7 @@ describe('test of buildPatientFilter', () => { 'age-day=ge3082', 'age-day=le9360' ] - expect(buildPatientFilter(selectedCriteria, false)).not.toEqual(error) + expect(buildFilter(selectedCriteria, false)).not.toEqual(error) }) }) @@ -239,7 +257,7 @@ describe('test of buildEncounterFilter', () => { it('should return default build Encounter', () => { const selectedCriteria: EncounterDataType = defaultEncounterCriteira const result = ['subject.active=true', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] - expect(buildEncounterFilter(selectedCriteria, false)).toEqual(result) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return complete build Encounter', () => { const selectedCriteria: EncounterDataType = completeEncounterCriteria @@ -263,20 +281,21 @@ describe('test of buildEncounterFilter', () => { '_filter=(period-start ge 2024-09-05T00:00:00Z and period-start le 2024-09-05T00:00:00Z) or not (period-start eq "*")', '_filter=(period-end ge 2024-09-06T00:00:00Z and period-end le 2024-09-07T00:00:00Z) or not (period-end eq "*")' ] - expect(buildEncounterFilter(selectedCriteria, false)).toEqual(result) + expect(buildFilter(selectedCriteria, false).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) }) describe('test of buildDocumentFilter', () => { it('should return default documentCriteria', () => { const selectedCriteria: DocumentDataType = defaultDocumentCriteria - const result = ['type:not=doc-impor&contenttype=text/plain&subject.active=true', '', '', '', '', '', '', '', '', ''] - expect(buildDocumentFilter(selectedCriteria)).toEqual(result) + const result = ['contenttype=text/plain&subject.active=true&type:not=doc-impor', '', '', '', '', '', '', '', '', ''] + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return complete documentCriteria', () => { const selectedCriteria: DocumentDataType = completeDocumentCriteria const result = [ - 'type:not=doc-impor&contenttype=text/plain&subject.active=true', + 'contenttype=text/plain', + 'subject.active=true&type:not=doc-impor', 'encounter.encounter-care-site=8312016825', '_text=cancer', 'docstatus=http://hl7.org/fhir/CodeSystem/composition-status|final,http://hl7.org/fhir/CodeSystem/composition-status|preliminary', @@ -287,7 +306,7 @@ describe('test of buildDocumentFilter', () => { '_filter=(encounter.period-start ge 2024-09-05T00:00:00Z and encounter.period-start le 2024-09-05T00:00:00Z) or not (encounter.period-start eq "*")', '_filter=(encounter.period-end ge 2024-09-06T00:00:00Z and encounter.period-end le 2024-09-07T00:00:00Z) or not (encounter.period-end eq "*")' ] - expect(buildDocumentFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) }) @@ -295,7 +314,7 @@ describe('test of buildConditionFilter', () => { it('should return default conditionCriteria', () => { const selectedCriteria: Cim10DataType = defaultConditionCriteria const result = ['subject.active=true', '', '', '', '', '', '', '', '', ''] - expect(buildConditionFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return complete conditionCriteria', () => { const selectedCriteria: Cim10DataType = completeConditionCriteria @@ -311,7 +330,7 @@ describe('test of buildConditionFilter', () => { '_filter=(encounter.period-start ge 2024-09-05T00:00:00Z and encounter.period-start le 2024-09-05T00:00:00Z) or not (encounter.period-start eq "*")', 'encounter.period-end=ge2024-09-06T00:00:00Z&encounter.period-end=le2024-09-07T00:00:00Z' ] - expect(buildConditionFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) }) @@ -319,7 +338,7 @@ describe('test of buildProcedureFilter', () => { it('should return default procedureCriteria', () => { const selectedCriteria: CcamDataType = defaultProcedureCriteria const result = ['subject.active=true', '', '', '', '', '', '', '', ''] - expect(buildProcedureFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return complete procedureCriteria ', () => { const selectedCriteria: CcamDataType = completeProcedureCriteria @@ -334,7 +353,7 @@ describe('test of buildProcedureFilter', () => { 'encounter.period-start=ge2024-09-05T00:00:00Z&encounter.period-start=le2024-09-05T00:00:00Z', '_filter=(encounter.period-end ge 2024-09-06T00:00:00Z and encounter.period-end le 2024-09-07T00:00:00Z) or not (encounter.period-end eq "*")' ] - expect(buildProcedureFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) }) @@ -342,7 +361,7 @@ describe('test of buildClaimFilter', () => { it('should return default claimCriteria', () => { const selectedCriteria: GhmDataType = defaultClaimCriteria const result = ['patient.active=true', '', '', '', '', '', '', ''] - expect(buildClaimFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return complete claimCriteria', () => { const selectedCriteria: GhmDataType = completeClaimCriteria @@ -356,7 +375,7 @@ describe('test of buildClaimFilter', () => { '_filter=(encounter.period-start ge 2024-09-04T00:00:00Z and encounter.period-start le 2024-09-07T00:00:00Z) or not (encounter.period-start eq "*")', 'encounter.period-end=ge2024-09-02T00:00:00Z&encounter.period-end=le2024-09-06T00:00:00Z' ] - expect(buildClaimFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) }) @@ -364,7 +383,7 @@ describe('test of buildMedicationFilter', () => { it('should return default Medication administation criteria', () => { const selectedCriteria: MedicationDataType = defaultMedicationCriteria const result = ['subject.active=true', '', '', '', '', '', '', '', '', '', '', ''] - expect(buildMedicationFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return complete Medication administation criteria', () => { const selectedCriteria: MedicationDataType = completeMedicationAdministrationCriteria @@ -382,7 +401,7 @@ describe('test of buildMedicationFilter', () => { '_filter=(context.period-start ge 2024-09-04T00:00:00Z and context.period-start le 2024-09-07T00:00:00Z) or not (context.period-start eq "*")', 'context.period-end=ge2024-09-02T00:00:00Z&context.period-end=le2024-09-06T00:00:00Z' ] - expect(buildMedicationFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return default Medication administation criteria', () => { const selectedCriteria: MedicationDataType = completeMedicationPrescriptionCriteria @@ -400,20 +419,20 @@ describe('test of buildMedicationFilter', () => { '_filter=(encounter.period-start ge 2024-09-04T00:00:00Z and encounter.period-start le 2024-09-07T00:00:00Z) or not (encounter.period-start eq "*")', 'encounter.period-end=ge2024-09-02T00:00:00Z&encounter.period-end=le2024-09-06T00:00:00Z' ] - expect(buildMedicationFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) }) describe('test of buildObservationFilter', () => { it('should return build default observation criteria', () => { const selectedCriteria: ObservationDataType = defaultObservationCriteria - const result = ['subject.active=true&status=Val', '', '', '', '', '', 'value-quantity=le0,ge0', '', ''] - expect(buildObservationFilter(selectedCriteria)).toEqual(result) + const result = ['status=Val&subject.active=true', '', '', '', '', '', 'value-quantity=le0,ge0', '', ''] + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return build complete obervation criteria', () => { const selectedCriteria: ObservationDataType = completeObservationCriteria const result = [ - 'subject.active=true&status=Val', + 'status=Val&subject.active=true', 'code=I3356', 'encounter.encounter-care-site=8312016825', 'encounter.status=cancelled', @@ -423,7 +442,7 @@ describe('test of buildObservationFilter', () => { '_filter=(encounter.period-start ge 2024-09-04T00:00:00Z and encounter.period-start le 2024-09-07T00:00:00Z) or not (encounter.period-start eq "*")', 'encounter.period-end=ge2024-09-02T00:00:00Z&encounter.period-end=le2024-09-06T00:00:00Z' ] - expect(buildObservationFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) }) @@ -443,7 +462,7 @@ describe('test of buildImagingFilter', () => { '', '', '', - 'numberOfSeries=1', + 'numberOfSeries=ge1', 'numberOfInstances=1', '', '', @@ -452,7 +471,7 @@ describe('test of buildImagingFilter', () => { '', '' ] - expect(buildImagingFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) it('should return build complete imaging criteria', () => { const selectedCriteria: ImagingDataType = completeImagingCriteria @@ -469,7 +488,7 @@ describe('test of buildImagingFilter', () => { '', '', '', - 'numberOfSeries=1', + 'numberOfSeries=ge1', 'numberOfInstances=1', 'encounter.status=cancelled', '', @@ -478,10 +497,11 @@ describe('test of buildImagingFilter', () => { '_filter=(encounter.period-start ge 2024-09-04T00:00:00Z and encounter.period-start le 2024-09-07T00:00:00Z) or not (encounter.period-start eq "*")', 'encounter.period-end=ge2024-09-02T00:00:00Z&encounter.period-end=le2024-09-06T00:00:00Z' ] - expect(buildImagingFilter(selectedCriteria)).toEqual(result) + expect(buildFilter(selectedCriteria).filter(el => !!el).sort().join("&")).toEqual(result.filter(el => !!el).sort().join("&")) }) }) + // describe('test of buildPregnancyFilter', () => { // it('', () => { // expect().toEqual() diff --git a/src/__tests__/data/cohortCreation/claimCriteria.ts b/src/__tests__/data/cohortCreation/claimCriteria.ts index 10eee8303..71b204d78 100644 --- a/src/__tests__/data/cohortCreation/claimCriteria.ts +++ b/src/__tests__/data/cohortCreation/claimCriteria.ts @@ -1,33 +1,20 @@ -import { GhmDataType, Comparators, CriteriaType } from 'types/requestCriterias' +import { + GhmDataType, + form +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/GHMForm' +import { Comparators } from 'types/requestCriterias' export const defaultClaimCriteria: GhmDataType = { id: 1, - type: CriteriaType.CLAIM, - isInclusive: true, - title: 'Claim', - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterStartDate: [null, null], - includeEncounterStartDateNull: false, - encounterEndDate: [null, null], - includeEncounterEndDateNull: false, - encounterStatus: [], - code: [], - label: undefined, - encounterService: undefined + ...form().initialData } export const completeClaimCriteria: GhmDataType = { ...defaultClaimCriteria, - occurrence: 1, - occurrenceComparator: Comparators.GREATER, - startOccurrence: ['2024-09-03', '2024-09-04'], - encounterStartDate: ['2024-09-04', '2024-09-07'], - includeEncounterStartDateNull: true, - encounterEndDate: ['2024-09-02', '2024-09-06'], - includeEncounterEndDateNull: false, + occurrence: { value: 1, comparator: Comparators.GREATER }, + startOccurrence: { start: '2024-09-03', end: '2024-09-04' }, + encounterStartDate: { start: '2024-09-04', end: '2024-09-07', includeNull: true }, + encounterEndDate: { start: '2024-09-02', end: '2024-09-06' }, encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], code: [ { diff --git a/src/__tests__/data/cohortCreation/cohortCreation.ts b/src/__tests__/data/cohortCreation/cohortCreation.ts index 14298159b..08ebf09ed 100644 --- a/src/__tests__/data/cohortCreation/cohortCreation.ts +++ b/src/__tests__/data/cohortCreation/cohortCreation.ts @@ -1,79 +1,46 @@ -import { CriteriaType, SelectedCriteriaType } from 'types/requestCriterias' - -const defaultProcedureCriteria: SelectedCriteriaType = { +import { + CcamDataType, + form as ccamForm +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/CCAMForm' +import { + DemographicDataType, + form as demographicForm +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DemographicForm' +import { + EncounterDataType, + form as encounterForm +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/EncounterForm' +import { + IPPListDataType, + form as ippForm +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/IPPForm' +import { SelectedCriteriaType } from 'types/requestCriterias' + +const defaultProcedureCriteria: CcamDataType = { id: 1, - error: undefined, - type: CriteriaType.PROCEDURE, - encounterService: undefined, - isInclusive: true, - title: 'Procedure', - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - endOccurrence: undefined, - encounterStartDate: [null, null], - includeEncounterStartDateNull: true, - encounterEndDate: [null, null], - includeEncounterEndDateNull: true, - encounterStatus: [], - hierarchy: undefined, - code: [], - source: null, - label: undefined + ...ccamForm().initialData } -const defaultPatientCriteria: SelectedCriteriaType = { +const defaultPatientCriteria: DemographicDataType = { id: 1, - title: 'Patient', - type: CriteriaType.PATIENT, - genders: [], - vitalStatus: [], - age: [null, null], - birthdates: [null, null], - deathDates: [null, null] + ...demographicForm().initialData } -const defaultEncounterCriteria: SelectedCriteriaType = { +const defaultEncounterCriteria: EncounterDataType = { id: 1, - type: CriteriaType.ENCOUNTER, - isInclusive: true, - title: 'critere encouter', - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterStartDate: [null, null], - includeEncounterStartDateNull: false, - encounterEndDate: [null, null], - includeEncounterEndDateNull: false, - encounterStatus: [], - age: [null, null], - duration: [null, null], - admissionMode: null, - entryMode: null, - exitMode: null, - priseEnChargeType: null, - typeDeSejour: null, - reason: null, - destination: null, - provenance: null, - admission: null, - encounterService: undefined + ...encounterForm().initialData } -const defaultIPPCriteria: SelectedCriteriaType = { +const defaultIPPCriteria: IPPListDataType = { id: 1, - type: CriteriaType.IPP_LIST, - isInclusive: true, - title: 'critere IPP', - search: '' + ...ippForm().initialData } export const procedurePeudonimizedCriteria: SelectedCriteriaType[] = [ { ...defaultProcedureCriteria, - startOccurrence: ['2024-08-15', '2024-08-22'], - encounterStartDate: ['2024-08-07', '2024-08-21'], + startOccurrence: { start: '2024-08-15', end: '2024-08-22' }, + encounterStartDate: { start: '2024-08-07', end: '2024-08-21' }, encounterStatus: [ { id: 'cancelled', @@ -81,7 +48,7 @@ export const procedurePeudonimizedCriteria: SelectedCriteriaType[] = [ system: 'http://hl7.org/fhir/CodeSystem/encounter-status' } ], - encounterEndDate: ['2024-08-22', '2024-08-22'], + encounterEndDate: { start: '2024-08-22', end: '2024-08-22' }, code: [ { id: '000212', @@ -120,35 +87,35 @@ export const patientPseudonimizedCriteria: SelectedCriteriaType[] = [ export const patientPseudonimizedAgeCriteria: SelectedCriteriaType[] = [ { ...defaultPatientCriteria, - age: ['0/2/12', '0/5/15'] + age: { start: '0/2/12', end: '0/5/15' } } ] export const patientNominativeAge0Criteria: SelectedCriteriaType[] = [ { ...defaultPatientCriteria, - age: ['7/2/12', '0/5/15'] + age: { start: '7/2/12', end: '0/5/15' } } ] export const patientNominativeAge1Criteria: SelectedCriteriaType[] = [ { ...defaultPatientCriteria, - age: ['0/2/12', '8/5/15'] + age: { start: '0/2/12', end: '8/5/15' } } ] export const patientNominativeBirthdates: SelectedCriteriaType[] = [ { ...defaultPatientCriteria, - birthdates: ['2024-08-15', '2024-08-15'] + birthdates: { start: '2024-08-15', end: '2024-08-15' } } ] export const patientNominativeDeathDates: SelectedCriteriaType[] = [ { ...defaultPatientCriteria, - deathDates: ['2024-08-15', '2024-08-15'] + deathDates: { start: '2024-08-15', end: '2024-08-15' } } ] @@ -168,7 +135,7 @@ export const criteriasArrayWtihNominativeData: SelectedCriteriaType[] = [ label: 'Vivant' } ], - deathDates: ['2024-08-15', '2024-08-15'] + deathDates: { start: '2024-08-15', end: '2024-08-15' } } ] @@ -188,7 +155,7 @@ export const criteriaArrayWithNoNominativeData: SelectedCriteriaType[] = [ label: 'Vivant' } ], - age: ['0/1/2', '0/5/15'] + age: { start: '0/1/2', end: '0/5/15' } } ] @@ -214,13 +181,13 @@ export const encounterPseudonimizedCriteria: SelectedCriteriaType[] = [ export const encounterPseudoAgeCriteria: SelectedCriteriaType[] = [ { ...defaultEncounterCriteria, - age: ['0/1/2', '0/5/15'] + age: { start: '0/1/2', end: '0/5/15' } } ] export const encounterNominativeAgeCriteria: SelectedCriteriaType[] = [ { ...defaultEncounterCriteria, - age: ['2/1/2', '5/5/15'] + age: { start: '2/1/2', end: '5/5/15' } } ] diff --git a/src/__tests__/data/cohortCreation/conditionCriteria.ts b/src/__tests__/data/cohortCreation/conditionCriteria.ts index 56f6cb7e8..6b9de0939 100644 --- a/src/__tests__/data/cohortCreation/conditionCriteria.ts +++ b/src/__tests__/data/cohortCreation/conditionCriteria.ts @@ -1,34 +1,21 @@ -import { Cim10DataType, Comparators, CriteriaType } from 'types/requestCriterias' +import { + Cim10DataType, + form +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/Cim10Form' +import { Comparators } from 'types/requestCriterias' export const defaultConditionCriteria: Cim10DataType = { id: 1, - type: CriteriaType.CONDITION, - isInclusive: true, - title: 'Condition', - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterStartDate: [null, null], - includeEncounterStartDateNull: false, - encounterEndDate: [null, null], - includeEncounterEndDateNull: false, - encounterStatus: [], - code: [], - source: null, - diagnosticType: null, - label: undefined + ...form().initialData, + source: null } export const completeConditionCriteria: Cim10DataType = { ...defaultConditionCriteria, - occurrence: 1, - occurrenceComparator: Comparators.GREATER, - startOccurrence: [null, null], - encounterStartDate: ['2024-09-05', '2024-09-05'], - includeEncounterStartDateNull: true, - encounterEndDate: ['2024-09-06', '2024-09-07'], - includeEncounterEndDateNull: false, + occurrence: { value: 1, comparator: Comparators.GREATER }, + startOccurrence: { start: null, end: null }, + encounterStartDate: { start: '2024-09-05', end: '2024-09-05', includeNull: true }, + encounterEndDate: { start: '2024-09-06', end: '2024-09-07' }, encounterStatus: [{ id: 'finished', label: 'Finished', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], code: [ { diff --git a/src/__tests__/data/cohortCreation/documentCriteria.ts b/src/__tests__/data/cohortCreation/documentCriteria.ts index e5b80a2ae..afdfb6168 100644 --- a/src/__tests__/data/cohortCreation/documentCriteria.ts +++ b/src/__tests__/data/cohortCreation/documentCriteria.ts @@ -1,42 +1,35 @@ -import { Comparators, CriteriaType, DocumentDataType } from 'types/requestCriterias' -import { SearchByTypes } from 'types/searchCriterias' +import { + DocumentDataType, + form +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DocumentsForm' +import { Comparators } from 'types/requestCriterias' +import { DocumentStatuses, FilterByDocumentStatus, SearchByTypes } from 'types/searchCriterias' export const defaultDocumentCriteria: DocumentDataType = { id: 1, - type: CriteriaType.DOCUMENTS, - isInclusive: true, - title: 'Document', - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterStartDate: [null, null], - includeEncounterStartDateNull: false, - encounterEndDate: [null, null], - includeEncounterEndDateNull: false, - encounterStatus: [], - docStatuses: [], - error: undefined, - docType: null, - search: '', - searchBy: SearchByTypes.TEXT, - encounterService: undefined + ...form().initialData } export const completeDocumentCriteria: DocumentDataType = { ...defaultDocumentCriteria, - occurrence: 1, - occurrenceComparator: Comparators.GREATER, - startOccurrence: ['2024-09-02', '2024-09-04'], - encounterStartDate: ['2024-09-05', '2024-09-05'], - includeEncounterStartDateNull: true, - encounterEndDate: ['2024-09-06', '2024-09-07'], - includeEncounterEndDateNull: true, + occurrence: { value: 1, comparator: Comparators.GREATER }, + startOccurrence: { start: '2024-09-02', end: '2024-09-04' }, + encounterStartDate: { start: '2024-09-05', end: '2024-09-05', includeNull: true }, + encounterEndDate: { start: '2024-09-06', end: '2024-09-07', includeNull: true }, encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], - docStatuses: ['Validé', 'Non validé'], + docStatuses: [ + { + id: DocumentStatuses.FINAL, + label: FilterByDocumentStatus.VALIDATED + }, + { + id: DocumentStatuses.PRELIMINARY, + label: FilterByDocumentStatus.NOT_VALIDATED + } + ], docType: [ - { type: 'Comptes Rendus Hospitalisation', label: 'CR de Jour', code: 'crh-j' }, - { type: 'Comptes Rendus Hospitalisation', label: 'CRH Chirurgie', code: 'crh-chir' } + { type: 'Comptes Rendus Hospitalisation', label: 'CR de Jour', id: 'crh-j' }, + { type: 'Comptes Rendus Hospitalisation', label: 'CRH Chirurgie', id: 'crh-chir' } ], search: 'cancer', searchBy: SearchByTypes.TEXT, diff --git a/src/__tests__/data/cohortCreation/encounterCriteria.ts b/src/__tests__/data/cohortCreation/encounterCriteria.ts index f8b12c30a..22714a888 100644 --- a/src/__tests__/data/cohortCreation/encounterCriteria.ts +++ b/src/__tests__/data/cohortCreation/encounterCriteria.ts @@ -1,43 +1,20 @@ -import { Comparators, CriteriaType, EncounterDataType } from 'types/requestCriterias' +import { + EncounterDataType, + form +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/EncounterForm' +import { Comparators } from 'types/requestCriterias' export const defaultEncounterCriteira: EncounterDataType = { id: 1, - type: CriteriaType.ENCOUNTER, - isInclusive: true, - title: 'Encouter', - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterStartDate: [null, null], - includeEncounterStartDateNull: false, - encounterEndDate: [null, null], - includeEncounterEndDateNull: false, - encounterStatus: [], - age: [null, null], - duration: [null, null], - admissionMode: null, - entryMode: null, - exitMode: null, - priseEnChargeType: null, - typeDeSejour: null, - reason: null, - destination: null, - provenance: null, - admission: null, - encounterService: undefined + ...form().initialData } export const completeEncounterCriteria: EncounterDataType = { ...defaultEncounterCriteira, - occurrence: 1, - occurrenceComparator: Comparators.GREATER, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterStartDate: ['2024-09-05', '2024-09-05'], - includeEncounterStartDateNull: true, - encounterEndDate: ['2024-09-06', '2024-09-07'], - includeEncounterEndDateNull: true, + occurrence: { value: 1, comparator: Comparators.GREATER }, + startOccurrence: null, + encounterStartDate: { start: '2024-09-05', end: '2024-09-05', includeNull: true }, + encounterEndDate: { start: '2024-09-06', end: '2024-09-07', includeNull: true }, encounterStatus: [ { id: 'status1', @@ -48,8 +25,8 @@ export const completeEncounterCriteria: EncounterDataType = { label: 'status2' } ], - age: ['3/2/1', '3/2/1'], - duration: ['6/5/4', '6/5/4'], + age: { start: '3/2/1', end: '3/2/1' }, + duration: { start: '6/5/4', end: '6/5/4' }, admissionMode: [ { id: 'mode1', diff --git a/src/__tests__/data/cohortCreation/imagingCriteria.ts b/src/__tests__/data/cohortCreation/imagingCriteria.ts index fc2a205ff..beb860b78 100644 --- a/src/__tests__/data/cohortCreation/imagingCriteria.ts +++ b/src/__tests__/data/cohortCreation/imagingCriteria.ts @@ -1,49 +1,19 @@ -import { ImagingDataType, Comparators, CriteriaType } from 'types/requestCriterias' -import { DocumentAttachmentMethod } from 'types/searchCriterias' +import { + ImagingDataType, + form +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/ImagingForm' +import { Comparators } from 'types/requestCriterias' export const defaultImagingCriteria: ImagingDataType = { id: 1, - type: CriteriaType.IMAGING, - isInclusive: true, - title: 'Imaging', - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterStartDate: [null, null], - includeEncounterStartDateNull: false, - encounterEndDate: [null, null], - includeEncounterEndDateNull: false, - encounterStatus: [], - studyStartDate: null, - studyEndDate: null, - studyModalities: [], - studyDescription: '', - studyProcedure: '', - numberOfSeries: 1, - seriesComparator: Comparators.EQUAL, - numberOfIns: 1, - instancesComparator: Comparators.EQUAL, - withDocument: DocumentAttachmentMethod.NONE, - daysOfDelay: null, - studyUid: '', - seriesStartDate: null, - seriesEndDate: null, - seriesDescription: '', - seriesProtocol: '', - seriesModalities: [], - seriesUid: '', - encounterService: [] + ...form().initialData, + numberOfIns: { value: 1, comparator: Comparators.EQUAL } } export const completeImagingCriteria: ImagingDataType = { ...defaultImagingCriteria, - occurrence: 1, - occurrenceComparator: Comparators.GREATER, - startOccurrence: ['2024-09-03', '2024-09-04'], - encounterStartDate: ['2024-09-04', '2024-09-07'], - includeEncounterStartDateNull: true, - encounterEndDate: ['2024-09-02', '2024-09-06'], - includeEncounterEndDateNull: false, + occurrence: { value: 1, comparator: Comparators.GREATER }, + encounterStartDate: { start: '2024-09-04', end: '2024-09-07', includeNull: true }, + encounterEndDate: { start: '2024-09-02', end: '2024-09-06' }, encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }] } diff --git a/src/__tests__/data/cohortCreation/medicationCriteria.ts b/src/__tests__/data/cohortCreation/medicationCriteria.ts index d5c64e833..43280355e 100644 --- a/src/__tests__/data/cohortCreation/medicationCriteria.ts +++ b/src/__tests__/data/cohortCreation/medicationCriteria.ts @@ -1,33 +1,22 @@ -import { MedicationDataType, Comparators, CriteriaType } from 'types/requestCriterias' +import { + MedicationDataType, + form +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/MedicationForm' +import { Comparators, CriteriaType } from 'types/requestCriterias' export const defaultMedicationCriteria: MedicationDataType = { id: 1, - type: CriteriaType.MEDICATION_ADMINISTRATION, - isInclusive: true, - title: 'Medciation', - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterStartDate: [null, null], - includeEncounterStartDateNull: false, - encounterEndDate: [null, null], - includeEncounterEndDateNull: false, - encounterStatus: [], - code: [], - administration: [], - encounterService: [] + ...form().initialData, + type: CriteriaType.MEDICATION_ADMINISTRATION } export const completeMedicationAdministrationCriteria: MedicationDataType = { ...defaultMedicationCriteria, - occurrence: 1, - occurrenceComparator: Comparators.GREATER, - startOccurrence: ['2024-09-03', '2024-09-04'], - encounterStartDate: ['2024-09-04', '2024-09-07'], - includeEncounterStartDateNull: true, - encounterEndDate: ['2024-09-02', '2024-09-06'], - includeEncounterEndDateNull: false, + type: CriteriaType.MEDICATION_ADMINISTRATION, + occurrence: { value: 1, comparator: Comparators.GREATER }, + startOccurrence: { start: '2024-09-03', end: '2024-09-04' }, + encounterStartDate: { start: '2024-09-04', end: '2024-09-07', includeNull: true }, + encounterEndDate: { start: '2024-09-02', end: '2024-09-06' }, encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], code: [ { id: 'D01AA01', label: 'D01AA01 - Nystatin; Topical', system: 'https://terminology.eds.aphp.fr/atc' }, @@ -66,13 +55,10 @@ export const completeMedicationAdministrationCriteria: MedicationDataType = { export const completeMedicationPrescriptionCriteria: MedicationDataType = { ...defaultMedicationCriteria, type: CriteriaType.MEDICATION_REQUEST, - occurrence: 1, - occurrenceComparator: Comparators.GREATER, - startOccurrence: ['2024-09-03', '2024-09-04'], - encounterStartDate: ['2024-09-04', '2024-09-07'], - includeEncounterStartDateNull: true, - encounterEndDate: ['2024-09-02', '2024-09-06'], - includeEncounterEndDateNull: false, + occurrence: { value: 1, comparator: Comparators.GREATER }, + startOccurrence: { start: '2024-09-03', end: '2024-09-04' }, + encounterStartDate: { start: '2024-09-04', end: '2024-09-07', includeNull: true }, + encounterEndDate: { start: '2024-09-02', end: '2024-09-06' }, encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], code: [ { id: 'D01AA01', label: 'D01AA01 - Nystatin; Topical', system: 'https://terminology.eds.aphp.fr/atc' }, diff --git a/src/__tests__/data/cohortCreation/observationCriteria.ts b/src/__tests__/data/cohortCreation/observationCriteria.ts index 6c1683b56..0a4e7c529 100644 --- a/src/__tests__/data/cohortCreation/observationCriteria.ts +++ b/src/__tests__/data/cohortCreation/observationCriteria.ts @@ -1,45 +1,30 @@ -import { ObservationDataType, Comparators, CriteriaType } from 'types/requestCriterias' +import { + ObservationDataType, + form +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/BiologyForm' +import { Comparators } from 'types/requestCriterias' export const defaultObservationCriteria: ObservationDataType = { id: 1, - type: CriteriaType.OBSERVATION, - isInclusive: true, - title: 'Observation', - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterStartDate: [null, null], - includeEncounterStartDateNull: false, - encounterEndDate: [null, null], - includeEncounterEndDateNull: false, - encounterStatus: [], - code: [], - isLeaf: false, - searchByValue: [null, null], - valueComparator: Comparators.EQUAL, - encounterService: [] + ...form().initialData } export const completeObservationCriteria: ObservationDataType = { ...defaultObservationCriteria, - occurrence: 1, - occurrenceComparator: Comparators.GREATER, - startOccurrence: ['2024-09-03', '2024-09-04'], - encounterStartDate: ['2024-09-04', '2024-09-07'], - includeEncounterStartDateNull: true, - encounterEndDate: ['2024-09-02', '2024-09-06'], - includeEncounterEndDateNull: false, + occurrence: { value: 1, comparator: Comparators.GREATER }, + startOccurrence: { start: '2024-09-03', end: '2024-09-04' }, + encounterStartDate: { start: '2024-09-04', end: '2024-09-07', includeNull: true }, + encounterEndDate: { start: '2024-09-02', end: '2024-09-06' }, encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], code: [ { id: 'I3356', label: 'I3356 - Erythrocytes Foetaux /érythrocytes Adultes_sang_cytochimie_hf/10000 Ha', - system: 'https://terminology.eds.aphp.fr/aphp-itm-anabio' + system: 'https://terminology.eds.aphp.fr/aphp-itm-anabio', + isLeaf: true } ], - isLeaf: true, - searchByValue: [3, null], + searchByValue: { value: 3, comparator: Comparators.EQUAL }, encounterService: [ { above_levels_ids: '8312002244', diff --git a/src/__tests__/data/cohortCreation/patientCriteria.ts b/src/__tests__/data/cohortCreation/patientCriteria.ts index b9f0d026b..8226ca1f4 100644 --- a/src/__tests__/data/cohortCreation/patientCriteria.ts +++ b/src/__tests__/data/cohortCreation/patientCriteria.ts @@ -1,17 +1,11 @@ -import { CriteriaType, DemographicDataType } from 'types/requestCriterias' +import { + DemographicDataType, + form +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DemographicForm' export const defaultPatientCriteria: DemographicDataType = { id: 1, - error: undefined, - type: CriteriaType.PATIENT, - encounterService: undefined, - isInclusive: true, - title: 'Demographic', - genders: null, - vitalStatus: null, - age: [null, null], - birthdates: [null, null], - deathDates: [null, null] + ...form().initialData } export const patientGenderFemaleCriteria: DemographicDataType = { @@ -63,17 +57,17 @@ export const patientVitalStatusCriteria: DemographicDataType = { export const patientNominativeAgeCriteria: DemographicDataType = { ...defaultPatientCriteria, - age: ['12/5/8', '25/7/25'] + age: { start: '12/5/8', end: '25/7/25' } } export const patientNominativeBirthDatesCriteria: DemographicDataType = { ...defaultPatientCriteria, - birthdates: ['2020-01-01', '2020-12-31'] + birthdates: { start: '2020-01-01', end: '2020-12-31' } } export const patientNominativeDeathDatesCriteria: DemographicDataType = { ...defaultPatientCriteria, - deathDates: ['2020-01-01', '2020-12-31'] + deathDates: { start: '2020-01-01', end: '2020-12-31' } } export const completePatientCriteria: DemographicDataType = { @@ -98,7 +92,7 @@ export const completePatientCriteria: DemographicDataType = { label: 'alive' } ], - age: ['12/5/8', '25/7/25'], - birthdates: ['2020-01-01', '2020-12-31'], - deathDates: ['2020-01-01', '2020-12-31'] + //age: { start: '12/5/8', end: '25/7/25' }, + birthdates: { start: '2020-01-01', end: '2020-12-31' }, + deathDates: { start: '2020-01-01', end: '2020-12-31' } } diff --git a/src/__tests__/data/cohortCreation/procedureCriteria.ts b/src/__tests__/data/cohortCreation/procedureCriteria.ts index 9901739f8..fe417fb54 100644 --- a/src/__tests__/data/cohortCreation/procedureCriteria.ts +++ b/src/__tests__/data/cohortCreation/procedureCriteria.ts @@ -1,35 +1,21 @@ -import { CcamDataType, Comparators, CriteriaType } from 'types/requestCriterias' +import { + CcamDataType, + form +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/CCAMForm' +import { Comparators } from 'types/requestCriterias' export const defaultProcedureCriteria: CcamDataType = { id: 1, - type: CriteriaType.PROCEDURE, - isInclusive: true, - title: 'Procedure', - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterStartDate: [null, null], - includeEncounterStartDateNull: false, - encounterEndDate: [null, null], - includeEncounterEndDateNull: false, - encounterStatus: [], - hierarchy: undefined, - code: [], - source: null, - label: undefined, - encounterService: undefined + ...form().initialData, + source: null } export const completeProcedureCriteria: CcamDataType = { ...defaultProcedureCriteria, - occurrence: 1, - occurrenceComparator: Comparators.GREATER, - startOccurrence: ['2024-09-06', '2024-09-06'], - encounterStartDate: ['2024-09-05', '2024-09-05'], - includeEncounterStartDateNull: false, - encounterEndDate: ['2024-09-06', '2024-09-07'], - includeEncounterEndDateNull: true, + occurrence: { value: 1, comparator: Comparators.GREATER }, + startOccurrence: { start: '2024-09-06', end: '2024-09-06' }, + encounterStartDate: { start: '2024-09-05', end: '2024-09-05' }, + encounterEndDate: { start: '2024-09-06', end: '2024-09-07', includeNull: true }, encounterStatus: [ { id: 'entered-in-error', label: 'Entered In Error', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' } ], diff --git a/src/components/CreationCohort/ControlPanel/styles.ts b/src/components/CreationCohort/ControlPanel/styles.ts index 8f87dacd6..cb08038ac 100644 --- a/src/components/CreationCohort/ControlPanel/styles.ts +++ b/src/components/CreationCohort/ControlPanel/styles.ts @@ -19,7 +19,7 @@ const useStyles = makeStyles()(() => ({ alignItems: 'center', marginTop: 8, padding: 4, - '&:first-child': { + '&:first-of-type': { marginTop: 0 } }, diff --git a/src/components/CreationCohort/DataList_Criteria.tsx b/src/components/CreationCohort/DataList_Criteria.tsx index 3fcf1d6d9..36aee792c 100644 --- a/src/components/CreationCohort/DataList_Criteria.tsx +++ b/src/components/CreationCohort/DataList_Criteria.tsx @@ -1,23 +1,41 @@ import { CriteriaItemType } from 'types' import RequestForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/RequestForm/RequestForm' -import IPPForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/IPPForm/IPPForm' -import DocumentsForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DocumentsForm/DocumentsForm' -import EncounterForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/EncounterForm' -import CCAMForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM' -import Cim10Form from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form' -import GhmForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM' -import MedicationForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm' -import BiologyForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm' -import DemographicForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DemographicForm' -import ImagingForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm' -import PregnantForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/PregnantForm' -import HospitForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm' -import services from 'services/aphp' - -import { CriteriaType, CriteriaTypeLabels } from 'types/requestCriterias' +import { CriteriaType } from 'types/requestCriterias' import { getConfig } from 'config' +import { form as ghmForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/GHMForm' +import { form as hospitForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/HospitForm' +import { form as imagingForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/ImagingForm' +import { form as cim10Form } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/Cim10Form' +import { form as ccamForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/CCAMForm' +import { form as demographicForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DemographicForm' +import { form as encounterForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/EncounterForm' +import { form as documentsForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DocumentsForm' +import { form as biologyForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/BiologyForm' +import { form as pregnantForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/PregnancyForm' +import { form as medicationForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/MedicationForm' +import { form as ippForm } from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/IPPForm' + +export enum CriteriaTypeLabels { + REQUEST = 'Mes requêtes', + IPP_LIST = "Liste d'IPP", + PATIENT = 'Démographie', + ENCOUNTER = 'Prise en charge', + DOCUMENTS = 'Documents cliniques', + PMSI = 'PMSI', + CONDITION = 'Diagnostics', + PROCEDURE = 'Actes', + CLAIM = 'GHM', + MEDICATION = 'Médicaments', + BIO_MICRO = 'Biologie/Microbiologie', + OBSERVATION = 'Biologie', + MICROBIOLOGIE = 'Microbiologie', + PHYSIOLOGIE = 'Physiologie', + IMAGING = 'Imagerie', + PREGNANCY = 'Fiche grossesse', + HOSPIT = "Fiche d'hospitalisation" +} const criteriaList: () => CriteriaItemType[] = () => { const ODD_QUESTIONNAIRE = getConfig().features.questionnaires.enabled @@ -30,14 +48,14 @@ const criteriaList: () => CriteriaItemType[] = () => { title: CriteriaTypeLabels.REQUEST, color: '#0063AF', fontWeight: 'bold', - components: RequestForm + component: RequestForm }, { id: CriteriaType.IPP_LIST, title: CriteriaTypeLabels.IPP_LIST, color: '#0063AF', fontWeight: 'bold', - components: IPPForm, + formDefinition: ippForm(), disabled: true }, { @@ -45,81 +63,48 @@ const criteriaList: () => CriteriaItemType[] = () => { title: CriteriaTypeLabels.PATIENT, color: '#0063AF', fontWeight: 'bold', - components: DemographicForm, - fetch: { gender: services.cohortCreation.fetchGender, status: services.cohortCreation.fetchStatus } + formDefinition: demographicForm() }, { id: CriteriaType.ENCOUNTER, title: CriteriaTypeLabels.ENCOUNTER, color: '#0063AF', fontWeight: 'bold', - components: EncounterForm, - fetch: { - admissionModes: services.cohortCreation.fetchAdmissionModes, - entryModes: services.cohortCreation.fetchEntryModes, - exitModes: services.cohortCreation.fetchExitModes, - priseEnChargeType: services.cohortCreation.fetchPriseEnChargeType, - typeDeSejour: services.cohortCreation.fetchTypeDeSejour, - fileStatus: services.cohortCreation.fetchFileStatus, - reason: services.cohortCreation.fetchReason, - destination: services.cohortCreation.fetchDestination, - provenance: services.cohortCreation.fetchProvenance, - admission: services.cohortCreation.fetchAdmission, - encounterStatus: services.cohortCreation.fetchEncounterStatus - } + formDefinition: encounterForm() }, { id: CriteriaType.DOCUMENTS, title: CriteriaTypeLabels.DOCUMENTS, color: '#0063AF', fontWeight: 'bold', - components: DocumentsForm, - fetch: { - docTypes: services.cohortCreation.fetchDocTypes, - encounterStatus: services.cohortCreation.fetchEncounterStatus - } + formDefinition: documentsForm() }, { id: CriteriaType.PMSI, title: CriteriaTypeLabels.PMSI, color: '#0063AF', fontWeight: 'bold', - components: null, subItems: [ { id: CriteriaType.CONDITION, title: CriteriaTypeLabels.CONDITION, color: '#0063AF', fontWeight: 'normal', - components: Cim10Form, - fetch: { - statusDiagnostic: services.cohortCreation.fetchStatusDiagnostic, - diagnosticTypes: services.cohortCreation.fetchDiagnosticTypes, - cim10Diagnostic: services.cohortCreation.fetchCim10Diagnostic, - encounterStatus: services.cohortCreation.fetchEncounterStatus - } + formDefinition: cim10Form() }, { id: CriteriaType.PROCEDURE, title: CriteriaTypeLabels.PROCEDURE, color: '#0063AF', fontWeight: 'normal', - components: CCAMForm, - fetch: { - ccamData: services.cohortCreation.fetchCcamData, - encounterStatus: services.cohortCreation.fetchEncounterStatus - } + formDefinition: ccamForm() }, { id: CriteriaType.CLAIM, title: CriteriaTypeLabels.CLAIM, color: '#0063AF', fontWeight: 'normal', - components: GhmForm, - fetch: { - ghmData: services.cohortCreation.fetchGhmData, - encounterStatus: services.cohortCreation.fetchEncounterStatus - } + formDefinition: ghmForm() } ] }, @@ -128,38 +113,26 @@ const criteriaList: () => CriteriaItemType[] = () => { title: 'Médicaments (Prescription - Administration)', color: ODD_MEDICATION ? '#0063AF' : '#808080', fontWeight: 'bold', - components: MedicationForm, - disabled: !ODD_MEDICATION, - fetch: { - medicationData: services.cohortCreation.fetchMedicationData, - prescriptionTypes: services.cohortCreation.fetchPrescriptionTypes, - administrations: services.cohortCreation.fetchAdministrations, - encounterStatus: services.cohortCreation.fetchEncounterStatus - } + formDefinition: medicationForm(), + disabled: !ODD_MEDICATION }, { id: CriteriaType.BIO_MICRO, title: CriteriaTypeLabels.BIO_MICRO, color: ODD_BIOLOGY ? '#0063AF' : '#808080', fontWeight: 'bold', - components: null, subItems: [ { id: CriteriaType.OBSERVATION, title: CriteriaTypeLabels.OBSERVATION, color: ODD_BIOLOGY ? '#0063AF' : '#808080', fontWeight: 'normal', - components: BiologyForm, - disabled: !ODD_BIOLOGY, - fetch: { - biologyData: services.cohortCreation.fetchBiologyData, - encounterStatus: services.cohortCreation.fetchEncounterStatus - } + formDefinition: biologyForm(), + disabled: !ODD_BIOLOGY }, { id: CriteriaType.MICROBIOLOGIE, title: CriteriaTypeLabels.MICROBIOLOGIE, - components: null, color: '#808080', fontWeight: 'normal', disabled: true @@ -171,12 +144,10 @@ const criteriaList: () => CriteriaItemType[] = () => { title: 'Dossiers de spécialité', color: '#0063AF', fontWeight: 'bold', - components: null, subItems: [ { id: CriteriaType.MATERNITY, title: 'Maternité', - components: null, color: '#0063AF', fontWeight: 'normal', subItems: [ @@ -186,17 +157,7 @@ const criteriaList: () => CriteriaItemType[] = () => { color: ODD_QUESTIONNAIRE ? '#0063AF' : '#808080', fontWeight: 'normal', disabled: !ODD_QUESTIONNAIRE, - components: PregnantForm, - fetch: { - pregnancyMode: services.cohortCreation.fetchPregnancyMode, - maternalRisks: services.cohortCreation.fetchMaternalRisks, - risksRelatedToObstetricHistory: services.cohortCreation.fetchRisksRelatedToObstetricHistory, - risksOrComplicationsOfPregnancy: services.cohortCreation.fetchRisksOrComplicationsOfPregnancy, - corticotherapie: services.cohortCreation.fetchCorticotherapie, - prenatalDiagnosis: services.cohortCreation.fetchPrenatalDiagnosis, - ultrasoundMonitoring: services.cohortCreation.fetchUltrasoundMonitoring, - encounterStatus: services.cohortCreation.fetchEncounterStatus - } + formDefinition: pregnantForm() }, { id: CriteriaType.HOSPIT, @@ -204,39 +165,7 @@ const criteriaList: () => CriteriaItemType[] = () => { color: ODD_QUESTIONNAIRE ? '#0063AF' : '#808080', fontWeight: 'normal', disabled: !ODD_QUESTIONNAIRE, - components: HospitForm, - fetch: { - inUteroTransfer: services.cohortCreation.fetchInUteroTransfer, - pregnancyMonitoring: services.cohortCreation.fetchPregnancyMonitoring, - maturationCorticotherapie: services.cohortCreation.fetchMaturationCorticotherapie, - chirurgicalGesture: services.cohortCreation.fetchChirurgicalGesture, - vme: services.cohortCreation.fetchVme, - childbirth: services.cohortCreation.fetchChildbirth, - hospitalChildBirthPlace: services.cohortCreation.fetchHospitalChildBirthPlace, - otherHospitalChildBirthPlace: services.cohortCreation.fetchOtherHospitalChildBirthPlace, - homeChildBirthPlace: services.cohortCreation.fetchHomeChildBirthPlace, - childbirthMode: services.cohortCreation.fetchChildbirthMode, - maturationReason: services.cohortCreation.fetchMaturationReason, - maturationModality: services.cohortCreation.fetchMaturationModality, - imgIndication: services.cohortCreation.fetchImgIndication, - laborOrCesareanEntry: services.cohortCreation.fetchLaborOrCesareanEntry, - pathologyDuringLabor: services.cohortCreation.fetchPathologyDuringLabor, - obstetricalGestureDuringLabor: services.cohortCreation.fetchObstetricalGestureDuringLabor, - analgesieType: services.cohortCreation.fetchAnalgesieType, - birthDeliveryWay: services.cohortCreation.fetchBirthDeliveryWay, - instrumentType: services.cohortCreation.fetchInstrumentType, - cSectionModality: services.cohortCreation.fetchCSectionModality, - presentationAtDelivery: services.cohortCreation.fetchPresentationAtDelivery, - birthStatus: services.cohortCreation.fetchBirthStatus, - postpartumHemorrhage: services.cohortCreation.fetchSetPostpartumHemorrhage, - conditionPerineum: services.cohortCreation.fetchConditionPerineum, - exitPlaceType: services.cohortCreation.fetchExitPlaceType, - feedingType: services.cohortCreation.fetchFeedingType, - complication: services.cohortCreation.fetchComplication, - exitFeedingMode: services.cohortCreation.fetchExitFeedingMode, - exitDiagnostic: services.cohortCreation.fetchExitDiagnostic, - encounterStatus: services.cohortCreation.fetchEncounterStatus - } + formDefinition: hospitForm() } ] } @@ -247,22 +176,28 @@ const criteriaList: () => CriteriaItemType[] = () => { title: CriteriaTypeLabels.IMAGING, color: ODD_IMAGING ? '#0063AF' : '#808080', fontWeight: 'bold', - components: ImagingForm, - disabled: !ODD_IMAGING, - fetch: { - modalities: services.cohortCreation.fetchModalities, - encounterStatus: services.cohortCreation.fetchEncounterStatus - } + formDefinition: imagingForm(), + disabled: !ODD_IMAGING }, { id: CriteriaType.PHYSIOLOGIE, title: CriteriaTypeLabels.PHYSIOLOGIE, color: '#808080', fontWeight: 'bold', - disabled: true, - components: null + disabled: true } ] } +export const getAllCriteriaItems = (criteria: readonly CriteriaItemType[]): CriteriaItemType[] => { + const allCriteriaItems: CriteriaItemType[] = [] + for (const criterion of criteria) { + allCriteriaItems.push(criterion) + if (criterion.subItems && criterion.subItems.length > 0) { + allCriteriaItems.push(...getAllCriteriaItems(criterion.subItems)) + } + } + return allCriteriaItems +} + export default criteriaList diff --git a/src/components/CreationCohort/DiagramView/components/CriteriaCard/index.tsx b/src/components/CreationCohort/DiagramView/components/CriteriaCard/index.tsx index 426df4c6f..0fe517cb4 100644 --- a/src/components/CreationCohort/DiagramView/components/CriteriaCard/index.tsx +++ b/src/components/CreationCohort/DiagramView/components/CriteriaCard/index.tsx @@ -14,10 +14,11 @@ import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' import { useAppSelector } from 'state' import useStyles from './styles' -import { criteriasAsArray } from 'utils/requestCriterias' import { ChipWrapper } from 'components/ui/Chip/styles' import { SelectedCriteriaType } from 'types/requestCriterias' import theme from 'theme' +import criteriaList, { getAllCriteriaItems } from 'components/CreationCohort/DataList_Criteria' +import { criteriasAsArray } from '../LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers' type CriteriaCardProps = { criterion: SelectedCriteriaType @@ -29,8 +30,9 @@ type CriteriaCardProps = { const CriteriaCard = ({ criterion, duplicateCriteria, editCriteria, deleteCriteria }: CriteriaCardProps) => { const { classes } = useStyles() - const { criteria } = useAppSelector((state) => state.cohortCreation) const maintenanceIsActive = useAppSelector((state) => state.me?.maintenance?.active || false) + const { entities, cache } = useAppSelector((state) => state.valueSets) + const criteriaDefinitions = getAllCriteriaItems(criteriaList()) const [needCollapse, setNeedCollapse] = useState(false) const [openCollapse, setOpenCollapse] = useState(false) @@ -81,7 +83,7 @@ const CriteriaCard = ({ criterion, duplicateCriteria, editCriteria, deleteCriter className={classes.secondItem} > - {criteriasAsArray(criterion, criteria).map((label, index) => ( + {criteriasAsArray(criterion, criteriaDefinitions, { entities, cache }).map((label, index) => ( void - onError: (error: boolean) => void -} - -const AdvancedInputs = ({ sourceType, selectedCriteria, onChangeValue, onError }: AdvancedInputsProps) => { - const optionsIsUsed = - (selectedCriteria.encounterService && selectedCriteria.encounterService.length > 0) || - selectedCriteria.startOccurrence?.[0] !== null || - selectedCriteria.startOccurrence?.[1] !== null || - selectedCriteria.endOccurrence?.[0] !== null || - selectedCriteria.endOccurrence?.[1] !== null || - selectedCriteria.encounterStartDate[0] !== null || - selectedCriteria.encounterStartDate[1] !== null || - selectedCriteria.encounterEndDate[0] !== null || - selectedCriteria.encounterEndDate[1] !== null - - const [checked, setChecked] = useState(optionsIsUsed) - - const _onSubmitExecutiveUnits = (_selectedExecutiveUnits: Hierarchy[]) => { - onChangeValue('encounterService', _selectedExecutiveUnits) - } - - return ( - - setChecked(!checked)} - > - - Options avancées - - - - {checked ? : } - - - - - - - - - - - - Début de prise en charge - - { - onChangeValue('encounterStartDate', newDate) - }} - onError={(isError) => onError(isError)} - includeNullValues={selectedCriteria.includeEncounterStartDateNull} - onChangeIncludeNullValues={(includeNullValues) => - onChangeValue('includeEncounterStartDateNull', includeNullValues) - } - /> - - Fin de prise en charge - - { - onChangeValue('encounterEndDate', newDate) - }} - onError={(isError) => onError(isError)} - includeNullValues={selectedCriteria.includeEncounterEndDateNull} - onChangeIncludeNullValues={(includeNullValues) => - onChangeValue('includeEncounterEndDateNull', includeNullValues) - } - /> - - - {selectedCriteria.type !== CriteriaType.IMAGING && ( - <> - - - { - onChangeValue('startOccurrence', newDate) - }} - onError={(isError) => onError(isError)} - /> - - {selectedCriteria.type === CriteriaType.MEDICATION_REQUEST && ( - - - { - onChangeValue('endOccurrence', newDate) - }} - onError={(isError) => onError(isError)} - /> - - )} - - )} - - - ) -} - -export default AdvancedInputs diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/BiologySearch/BiologySearch.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/BiologySearch/BiologySearch.tsx index 1f5abaa5d..16992580b 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/BiologySearch/BiologySearch.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/BiologySearch/BiologySearch.tsx @@ -28,9 +28,9 @@ import useStyles from './styles' import { useDebounce } from 'utils/debounce' import { ValueSetWithHierarchy } from 'services/aphp/cohortCreation/fetchObservation' import services from 'services/aphp' -import { ObservationDataType } from 'types/requestCriterias' import { ValueSet } from 'types' import { Hierarchy } from 'types/hierarchy' +import { ObservationDataType } from '../../../forms/BiologyForm' type BiologySearchListItemProps = { label: string diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Form/BiologyForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Form/BiologyForm.tsx deleted file mode 100644 index 80f47e06d..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Form/BiologyForm.tsx +++ /dev/null @@ -1,403 +0,0 @@ -import React, { useEffect, useState } from 'react' - -import { - Alert, - Autocomplete, - Button, - Checkbox, - Chip, - Divider, - FormLabel, - Grid, - IconButton, - MenuItem, - Select, - Switch, - TextField, - Tooltip, - Typography -} from '@mui/material' - -import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace' - -import useStyles from './styles' -import { useAppDispatch, useAppSelector } from 'state' -import { fetchBiology } from 'state/biology' -import { CriteriaItemDataCache, HierarchyTree } from 'types' -import AdvancedInputs from '../../../AdvancedInputs/AdvancedInputs' -import { ObservationDataType, Comparators, SelectedCriteriaType } from 'types/requestCriterias' -import services from 'services/aphp' -import { BlockWrapper } from 'components/ui/Layout' -import OccurenceInput from 'components/ui/Inputs/Occurences' -import { ErrorWrapper } from 'components/ui/Searchbar/styles' - -enum Error { - NO_ERROR, - INCOHERENT_VALUE_ERROR, - INVALID_VALUE_ERROR, - MISSING_VALUE_ERROR, - ADVANCED_INPUTS_ERROR -} -import { SourceType } from 'types/scope' -import { Hierarchy } from 'types/hierarchy' -import { CriteriaLabel } from 'components/ui/CriteriaLabel' - -type BiologyFormProps = { - isOpen: boolean - isEdition: boolean - criteriaData: CriteriaItemDataCache - selectedCriteria: ObservationDataType - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onChangeValue: (key: string, value: any) => void - goBack: () => void - onChangeSelectedCriteria: (data: SelectedCriteriaType) => void -} - -const BiologyForm: React.FC = (props) => { - const { isOpen, isEdition, criteriaData, selectedCriteria, onChangeValue, onChangeSelectedCriteria, goBack } = props - - const { classes } = useStyles() - const dispatch = useAppDispatch() - const initialState: HierarchyTree | null = useAppSelector((state) => state.syncHierarchyTable) - const currentState = { ...selectedCriteria, ...initialState } - const [multiFields, setMultiFields] = useState(localStorage.getItem('multiple_fields')) - const [allowSearchByValue, setAllowSearchByValue] = useState( - typeof currentState.searchByValue[0] === 'number' || typeof currentState.searchByValue[1] === 'number' - ) - const [occurrence, setOccurrence] = useState(currentState.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - currentState.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [error, setError] = useState(Error.NO_ERROR) - const [_searchByValues, setSearchByValues] = useState<[string, string]>([ - currentState.searchByValue[0] !== null ? currentState.searchByValue[0].toString() : '', - currentState.searchByValue[1] !== null ? currentState.searchByValue[1].toString() : '' - ]) - - const _onSubmit = () => { - const parseSearchByValue = (value: [string, string]): [number | null, number | null] => { - return [value[0] ? parseFloat(value[0]) : null, value[1] ? parseFloat(value[1]) : null] - } - onChangeSelectedCriteria({ - ...currentState, - occurrence: occurrence, - occurrenceComparator: occurrenceComparator, - searchByValue: parseSearchByValue(_searchByValues) - }) - dispatch(fetchBiology()) - } - - const defaultValuesCode = currentState.code - ? currentState.code.map((code) => { - const criteriaCode = criteriaData.data.biologyData - ? criteriaData.data.biologyData.find((g: Hierarchy) => g.id === code.id) - : null - return { - id: code.id, - label: code.label ? code.label : criteriaCode?.label ?? '?' - } - }) - : [] - - useEffect(() => { - const checkChildren = async () => { - try { - const getChildrenResp = await services.cohortCreation.fetchBiologyHierarchy(selectedCriteria.code?.[0].id) - - getChildrenResp?.length > 0 ? onChangeValue('isLeaf', false) : onChangeValue('isLeaf', true) - } catch (error) { - console.error('Erreur lors du check des enfants du code de biologie sélectionné', error) - } - } - - selectedCriteria?.code?.length === 1 && selectedCriteria?.code[0].id !== '*' - ? checkChildren() - : onChangeValue('isLeaf', false) - }, [currentState?.code]) - - useEffect(() => { - if (!currentState.isLeaf) { - setAllowSearchByValue(false) - } - }, [currentState.isLeaf]) - - useEffect(() => { - if (!allowSearchByValue) { - setSearchByValues(['', '']) - onChangeValue('searchByValue', [null, null]) - } - }, [allowSearchByValue]) - - useEffect(() => { - const floatRegex = /^-?\d*\.?\d*$/ // matches numbers, with decimals or not, negative or not - - if ( - (_searchByValues[0] && !_searchByValues[0].match(floatRegex)) || - (_searchByValues[1] && !_searchByValues[1].match(floatRegex)) - ) { - setError(Error.INVALID_VALUE_ERROR) - } else if ( - _searchByValues[0] && - _searchByValues[1] && - parseFloat(_searchByValues[0]) > parseFloat(_searchByValues[1]) - ) { - setError(Error.INCOHERENT_VALUE_ERROR) - } else if ( - allowSearchByValue && - currentState.valueComparator === Comparators.BETWEEN && - (!_searchByValues[0] || !_searchByValues[1]) - ) { - setError(Error.MISSING_VALUE_ERROR) - } else { - setError(Error.NO_ERROR) - } - }, [_searchByValues, currentState.valueComparator, allowSearchByValue]) - - const handleValueChange = (newValue: string, index: number) => { - const invalidCharRegex = /[^0-9.-]/ // matches everything that is not a number, a "," or a "." - - if (!newValue.match(invalidCharRegex)) { - const parsedNewValue = newValue !== '' ? newValue : '' - setSearchByValues(index === 0 ? [parsedNewValue, _searchByValues[1]] : [_searchByValues[0], parsedNewValue]) - } - } - - return isOpen ? ( - - - {!isEdition ? ( - <> - - - - - Ajouter un critère de biologie - - ) : ( - Modifier un critère de biologie - )} - - - - {!multiFields && ( - { - localStorage.setItem('multiple_fields', 'ok') - setMultiFields('ok') - }} - > - Tous les éléments des champs multiples sont liés par une contrainte OU - - )} - - - Les mesures de biologie sont pour l'instant restreintes aux 3870 codes ANABIO correspondants aux analyses les - plus utilisées au niveau national et à l'AP-HP. De plus, les résultats concernent uniquement les analyses - quantitatives enregistrées sur GLIMS, qui ont été validées et mises à jour depuis mars 2020. - - - - Biologie - - onChangeValue('title', e.target.value)} - /> - - - onChangeValue('isInclusive', !currentState.isInclusive)} - style={{ margin: 'auto 1em' }} - component="legend" - > - Exclure les patients qui suivent les règles suivantes - - onChangeValue('isInclusive', !event.target.checked)} - color="secondary" - /> - - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - withHierarchyInfo - /> - - - - - - - {defaultValuesCode.length > 0 ? ( - defaultValuesCode.map((valueCode, index: number) => ( - - - {valueCode.label} - - - } - onDelete={() => - onChangeValue( - 'code', - defaultValuesCode.filter((code) => code !== valueCode) - ) - } - /> - )) - ) : ( - - Veuillez ajouter des codes de biologie via les onglets Hiérarchie ou Recherche. - - )} - - - - - setAllowSearchByValue(!allowSearchByValue)} - disabled={!currentState.isLeaf} - /> - - - - handleValueChange(e.target.value, 0)} - placeholder={currentState.valueComparator === Comparators.BETWEEN ? 'Valeur minimale' : '0'} - disabled={!allowSearchByValue} - error={ - error === Error.INCOHERENT_VALUE_ERROR || - error === Error.INVALID_VALUE_ERROR || - error === Error.MISSING_VALUE_ERROR - } - /> - {currentState.valueComparator === Comparators.BETWEEN && ( - handleValueChange(e.target.value, 1)} - placeholder="Valeur maximale" - disabled={!allowSearchByValue} - error={ - error === Error.INCOHERENT_VALUE_ERROR || - error === Error.INVALID_VALUE_ERROR || - error === Error.MISSING_VALUE_ERROR - } - /> - )} - - - - {error === Error.INCOHERENT_VALUE_ERROR && ( - - La valeur minimale ne peut pas être supérieure à la valeur maximale. - - )} - {error === Error.INVALID_VALUE_ERROR && ( - Veuillez entrer un nombre valide. - )} - {error === Error.MISSING_VALUE_ERROR && ( - Veuillez entrer 2 valeurs avec ce comparateur. - )} - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={currentState.encounterStatus} - onChange={(e, value) => onChangeValue('encounterStatus', value)} - renderInput={(params) => } - /> - - - setError(isError ? Error.ADVANCED_INPUTS_ERROR : Error.NO_ERROR)} - /> - - - - {!isEdition && ( - - )} - - - - - ) : ( - <> - ) -} - -export default BiologyForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Hierarchy/BiologyHierarchy.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Hierarchy/BiologyHierarchy.tsx index 5dbfd2da1..ff1922002 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Hierarchy/BiologyHierarchy.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Hierarchy/BiologyHierarchy.tsx @@ -32,9 +32,9 @@ import { import useStyles from './styles' import { decrementLoadingSyncHierarchyTable, incrementLoadingSyncHierarchyTable } from 'state/syncHierarchyTable' import { findSelectedInListAndSubItems } from 'utils/cohortCreation' -import { defaultBiology } from '../../index' import { HierarchyTree } from 'types' import { Hierarchy } from 'types/hierarchy' +import { CriteriaType } from 'types/requestCriterias' type BiologyListItemProps = { biologyItem: Hierarchy @@ -64,7 +64,7 @@ const BiologyListItem: React.FC = (props) => { biologyCode, selectedItems || [], biologyHierarchy, - defaultBiology.type, + CriteriaType.OBSERVATION, dispatch ) handleClick(selectedItems, newHierarchy) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/index.tsx index 459cf14d1..0a8a23d95 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/index.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/index.tsx @@ -3,42 +3,28 @@ import { Tabs, Tab } from '@mui/material' import useStyles from './styles' -import BiologyForm from './components/Form/BiologyForm' import BiologyHierarchy from './components/Hierarchy/BiologyHierarchy' import BiologySearch from './components/BiologySearch/BiologySearch' import { initSyncHierarchyTableEffect, syncOnChangeFormValue } from 'utils/pmsi' import { fetchBiology } from 'state/biology' import { useAppDispatch, useAppSelector } from 'state' -import { Comparators, ObservationDataType, CriteriaType } from 'types/requestCriterias' +import { CriteriaType } from 'types/requestCriterias' import { CriteriaDrawerComponentProps } from 'types' import { Hierarchy } from 'types/hierarchy' import { AppConfig } from 'config' - -export const defaultBiology: Omit = { - type: CriteriaType.OBSERVATION, - title: 'Critères de biologie', - code: [], - isLeaf: false, - valueComparator: Comparators.GREATER_OR_EQUAL, - searchByValue: [null, null], - occurrence: 1, - occurrenceComparator: Comparators.GREATER_OR_EQUAL, - startOccurrence: [null, null], - isInclusive: true, - encounterStartDate: [null, null], - encounterEndDate: [null, null], - encounterStatus: [] -} +import { ObservationDataType, form } from '../forms/BiologyForm' +import { fetchValueSet } from 'services/aphp/callApi' +import CriteriaForm from '../CriteriaForm' const Index = (props: CriteriaDrawerComponentProps) => { - const { criteriaData, selectedCriteria, onChangeSelectedCriteria, goBack } = props + const { selectedCriteria, onChangeSelectedCriteria, goBack } = props const config = useContext(AppConfig) const { classes } = useStyles() const [selectedTab, setSelectedTab] = useState<'form' | 'hierarchy' | 'search'>( selectedCriteria ? 'form' : 'hierarchy' ) const [defaultCriteria, setDefaultCriteria] = useState( - (selectedCriteria as ObservationDataType) || defaultBiology + (selectedCriteria as ObservationDataType) || { ...form().initialData } ) const isEdition = selectedCriteria !== null @@ -59,7 +45,7 @@ const Index = (props: CriteriaDrawerComponentProps) => { newHierarchy, setDefaultCriteria, selectedTab, - defaultBiology.type, + CriteriaType.OBSERVATION, dispatch ) const _initSyncHierarchyTableEffect = async () => { @@ -68,7 +54,7 @@ const Index = (props: CriteriaDrawerComponentProps) => { selectedCriteria, defaultCriteria && defaultCriteria.code ? defaultCriteria.code : [], fetchBiology, - defaultBiology.type, + CriteriaType.OBSERVATION, dispatch ) } @@ -88,17 +74,21 @@ const Index = (props: CriteriaDrawerComponentProps) => { - { - + fetchValueSet( + codeSystemUrl, + { valueSetTitle: 'Toute la hiérarchie', search: code, noStar: false }, + abortSignal + ) + } /> - } + )} {selectedTab === 'search' && ( void - goBack: () => void - onChangeSelectedCriteria: (data: SelectedCriteriaType) => void -} - -enum Error { - ADVANCED_INPUTS_ERROR, - NO_ERROR -} - -const CcamForm: React.FC = (props) => { - const { isOpen, isEdition, criteriaData, selectedCriteria, onChangeValue, onChangeSelectedCriteria, goBack } = props - - const { classes } = useStyles() - const dispatch = useAppDispatch() - const initialState: HierarchyTree | null = useAppSelector((state) => state.syncHierarchyTable) - const currentState = { ...selectedCriteria, ...initialState } - const [multiFields, setMultiFields] = useState(localStorage.getItem('multiple_fields')) - const [occurrence, setOccurrence] = useState(currentState.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - currentState.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [error, setError] = useState(Error.NO_ERROR) - - const _onSubmit = () => { - onChangeSelectedCriteria({ ...currentState, occurrence: occurrence, occurrenceComparator: occurrenceComparator }) - dispatch(fetchProcedure()) - } - - const getCCAMOptions = async (searchValue: string, signal: AbortSignal) => { - const ccamOptions = await services.cohortCreation.fetchCcamData(searchValue, false, signal) - - return ccamOptions && ccamOptions.length > 0 ? ccamOptions : [] - } - - const defaultValuesCode = currentState.code - ? currentState.code.map((code) => { - const criteriaCode = criteriaData.data.ccamData - ? criteriaData.data.ccamData.find((g: Hierarchy) => g.id === code.id) - : null - return { - id: code.id, - label: code.label ? code.label : criteriaCode?.label ?? '?' - } - }) - : [] - - return isOpen ? ( - - - {!isEdition ? ( - <> - - - - - Ajouter un critère d'acte CCAM - - ) : ( - Modifier un critère d'acte CCAM - )} - - - - {!multiFields && ( - { - localStorage.setItem('multiple_fields', 'ok') - setMultiFields('ok') - }} - > - Tous les éléments des champs multiples sont liés par une contrainte OU - - )} - - - Actes CCAM - - onChangeValue('title', e.target.value)} - /> - - - onChangeValue('isInclusive', !currentState.isInclusive)} - style={{ margin: 'auto 1em' }} - component="legend" - > - Exclure les patients qui suivent les règles suivantes - - onChangeValue('isInclusive', !event.target.checked)} - color="secondary" - /> - - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - withHierarchyInfo - /> - - - - onChangeValue('source', value)} - > - } label="AREM" /> - } label="ORBIS" /> - - - - - - Les données AREM sont disponibles uniquement pour la période du 07/12/2009 au 31/12/2022. - - - Seuls les actes rattachés à une visite Orbis (avec un Dossier Administratif - NDA) sont actuellement - disponibles. - - - Les données PMSI d'ORBIS sont codées au quotidien par les médecins. Les données PMSI AREM sont validées, - remontées aux tutelles et disponibles dans le SNDS. - - - - onChangeValue('code', value)} - /> - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={currentState.encounterStatus} - onChange={(e, value) => onChangeValue('encounterStatus', value)} - renderInput={(params) => } - /> - - setError(isError ? Error.ADVANCED_INPUTS_ERROR : Error.NO_ERROR)} - /> - - - - {!isEdition && ( - - )} - - - - - ) : ( - <> - ) -} - -export default CcamForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/components/Form/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/components/Form/styles.ts deleted file mode 100644 index 8765a8bcb..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/components/Form/styles.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - root: { - display: 'flex', - flexDirection: 'column', - flex: '1 1 auto' - }, - actionContainer: { - display: 'flex', - alignItems: 'center', - height: 72, - padding: 20, - backgroundColor: '#317EAA', - color: 'white', - // Not default - marginBottom: 46 - }, - backButton: { color: 'white' }, - divider: { background: 'white' }, - titleLabel: { marginLeft: '1em' }, - formContainer: { - overflow: 'auto', - maxHeight: 'calc(100vh - 183px)' - }, - inputContainer: { - padding: '1em', - display: 'flex', - flex: '1 1 0%', - flexDirection: 'column' - }, - inputItem: { - margin: '1em', - width: 'calc(100% - 2em)' - }, - criteriaActionContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexWrap: 'wrap', - borderTop: '1px solid grey', - position: 'absolute', - width: '100%', - bottom: 0, - left: 0, - background: '#fff', - '& > button': { - margin: '12px 8px' - } - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/components/Hierarchy/CCAMHierarchy.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/components/Hierarchy/CCAMHierarchy.tsx index a07744a8b..9e86f9af3 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/components/Hierarchy/CCAMHierarchy.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/components/Hierarchy/CCAMHierarchy.tsx @@ -32,10 +32,10 @@ import { import useStyles from './styles' import { decrementLoadingSyncHierarchyTable, incrementLoadingSyncHierarchyTable } from 'state/syncHierarchyTable' import { findSelectedInListAndSubItems } from 'utils/cohortCreation' -import { defaultProcedure } from '../../index' import { HierarchyTree } from 'types' -import { CcamDataType } from 'types/requestCriterias' import { Hierarchy } from 'types/hierarchy' +import { CcamDataType } from '../../../forms/CCAMForm' +import { CriteriaType } from 'types/requestCriterias' type ProcedureListItemProps = { procedureItem: Hierarchy @@ -65,7 +65,7 @@ const ProcedureListItem: React.FC = (props) => { procedureCode, selectedItems || [], procedureHierarchy, - defaultProcedure.type, + CriteriaType.PROCEDURE, dispatch ) handleClick(selectedItems, newHierarchy) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/index.tsx index c395cf165..a8bbf97ba 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/index.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM/index.tsx @@ -3,7 +3,6 @@ import { Tab, Tabs } from '@mui/material' import useStyles from './styles' -import CcamForm from './components/Form/CCAMForm' import CcamHierarchy from './components/Hierarchy/CCAMHierarchy' import { initSyncHierarchyTableEffect, syncOnChangeFormValue } from 'utils/pmsi' import { useAppDispatch, useAppSelector } from 'state' @@ -11,30 +10,17 @@ import { fetchProcedure } from 'state/pmsi' import { EXPLORATION } from '../../../../../../../..//constants' import { CriteriaDrawerComponentProps } from 'types' -import { CcamDataType, Comparators, CriteriaType } from 'types/requestCriterias' +import { CriteriaType } from 'types/requestCriterias' import { Hierarchy } from 'types/hierarchy' - -export const defaultProcedure: Omit = { - type: CriteriaType.PROCEDURE, - title: "Critères d'actes CCAM", - label: undefined, - code: [], - source: 'AREM', - occurrence: 1, - hierarchy: undefined, - occurrenceComparator: Comparators.GREATER_OR_EQUAL, - startOccurrence: [null, null], - isInclusive: true, - encounterStartDate: [null, null], - encounterEndDate: [null, null], - encounterStatus: [] -} +import { CcamDataType, form } from '../forms/CCAMForm' +import CriteriaForm from '../CriteriaForm' +import { fetchValueSet } from 'services/aphp/callApi' const Index = (props: CriteriaDrawerComponentProps) => { - const { criteriaData, selectedCriteria, onChangeSelectedCriteria, goBack } = props + const { selectedCriteria, onChangeSelectedCriteria, goBack } = props const [selectedTab, setSelectedTab] = useState<'form' | 'hierarchy'>(selectedCriteria ? 'form' : 'hierarchy') const [defaultCriteria, setDefaultCriteria] = useState( - (selectedCriteria as CcamDataType) || defaultProcedure + (selectedCriteria as CcamDataType) || { ...form().initialData } ) const isEdition = selectedCriteria !== null @@ -56,7 +42,7 @@ const Index = (props: CriteriaDrawerComponentProps) => { newHierarchy, setDefaultCriteria, selectedTab, - defaultProcedure.type, + CriteriaType.PROCEDURE, dispatch ) const _initSyncHierarchyTableEffect = async () => { @@ -65,7 +51,7 @@ const Index = (props: CriteriaDrawerComponentProps) => { selectedCriteria, defaultCriteria && defaultCriteria.code ? defaultCriteria.code : [], fetchProcedure, - defaultProcedure.type, + CriteriaType.PROCEDURE, dispatch ) } @@ -85,17 +71,21 @@ const Index = (props: CriteriaDrawerComponentProps) => { - { - + fetchValueSet( + codeSystemUrl, + { valueSetTitle: 'Toute la hiérarchie', search: code, noStar: false }, + abortSignal + ) + } /> - } + )} { void - goBack: () => void - onChangeSelectedCriteria: (data: SelectedCriteriaType) => void -} - -enum Error { - ADVANCED_INPUTS_ERROR, - NO_ERROR -} - -const Cim10Form: React.FC = (props) => { - const { isOpen, isEdition, criteriaData, selectedCriteria, onChangeValue, onChangeSelectedCriteria, goBack } = props - - const { classes } = useStyles() - const dispatch = useAppDispatch() - const initialState: HierarchyTree | null = useAppSelector((state) => state.syncHierarchyTable) - const currentState = { ...selectedCriteria, ...initialState } - const [multiFields, setMultiFields] = useState(localStorage.getItem('multiple_fields')) - const _onSubmit = () => { - onChangeSelectedCriteria({ ...currentState, occurrence: occurrence, occurrenceComparator: occurrenceComparator }) - dispatch(fetchCondition()) - } - const [occurrence, setOccurrence] = useState(currentState.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - currentState.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [error, setError] = useState(Error.NO_ERROR) - - const getDiagOptions = async (searchValue: string, signal: AbortSignal) => - await services.cohortCreation.fetchCim10Diagnostic(searchValue, false, signal) - - const defaultValuesCode = currentState.code - ? currentState.code.map((code) => { - const criteriaCode = criteriaData.data.cim10Diagnostic - ? criteriaData.data.cim10Diagnostic.find((c: Hierarchy) => c.id === code.id) - : null - return { - id: code.id, - label: code.label ? code.label : criteriaCode?.label ?? '?' - } - }) - : [] - const defaultValuesType = currentState.diagnosticType - ? currentState.diagnosticType.map((diagnosticType) => { - const criteriaType = criteriaData.data.diagnosticTypes - ? criteriaData.data.diagnosticTypes.find((g: Hierarchy) => g.id === diagnosticType.id) - : null - return { - id: diagnosticType.id, - label: diagnosticType.label ? diagnosticType.label : criteriaType?.label ?? '?' - } - }) - : [] - - return isOpen ? ( - - - {!isEdition ? ( - <> - - - - - Ajouter un critère de diagnostic - - ) : ( - Modifier un critère de diagnostic - )} - - - - {!multiFields && ( - { - localStorage.setItem('multiple_fields', 'ok') - setMultiFields('ok') - }} - > - Tous les éléments des champs multiples sont liés par une contrainte OU - - )} - - - Diagnostic - onChangeValue('title', e.target.value)} - /> - - onChangeValue('isInclusive', !currentState.isInclusive)} - style={{ margin: 'auto 1em' }} - component="legend" - > - Exclure les patients qui suivent les règles suivantes - - onChangeValue('isInclusive', !event.target.checked)} - color="secondary" - /> - - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - withHierarchyInfo - /> - - - - onChangeValue('source', value)} - > - } label="AREM" /> - } label="ORBIS" /> - - - - - - Les données AREM sont disponibles uniquement pour la période du 07/12/2009 au 31/12/2022. - - - Seuls les diagnostics rattachés à une visite Orbis (avec un Dossier Administratif - NDA) sont actuellement - disponibles. - - - Les données PMSI d'ORBIS sont codées au quotidien par les médecins. Les données PMSI AREM sont validées, - remontées aux tutelles et disponibles dans le SNDS. - - - - { - onChangeValue('code', value) - }} - /> - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={defaultValuesType} - onChange={(e, value) => onChangeValue('diagnosticType', value)} - renderInput={(params) => } - /> - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={currentState.encounterStatus} - onChange={(e, value) => onChangeValue('encounterStatus', value)} - renderInput={(params) => } - /> - setError(isError ? Error.ADVANCED_INPUTS_ERROR : Error.NO_ERROR)} - /> - - - - {!isEdition && ( - - )} - - - - - ) : ( - <> - ) -} - -export default Cim10Form diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/components/Form/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/components/Form/styles.ts deleted file mode 100644 index 8765a8bcb..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/components/Form/styles.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - root: { - display: 'flex', - flexDirection: 'column', - flex: '1 1 auto' - }, - actionContainer: { - display: 'flex', - alignItems: 'center', - height: 72, - padding: 20, - backgroundColor: '#317EAA', - color: 'white', - // Not default - marginBottom: 46 - }, - backButton: { color: 'white' }, - divider: { background: 'white' }, - titleLabel: { marginLeft: '1em' }, - formContainer: { - overflow: 'auto', - maxHeight: 'calc(100vh - 183px)' - }, - inputContainer: { - padding: '1em', - display: 'flex', - flex: '1 1 0%', - flexDirection: 'column' - }, - inputItem: { - margin: '1em', - width: 'calc(100% - 2em)' - }, - criteriaActionContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexWrap: 'wrap', - borderTop: '1px solid grey', - position: 'absolute', - width: '100%', - bottom: 0, - left: 0, - background: '#fff', - '& > button': { - margin: '12px 8px' - } - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/components/Hierarchy/Cim10Hierarchy.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/components/Hierarchy/Cim10Hierarchy.tsx index f92b1e2ce..dc4536b66 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/components/Hierarchy/Cim10Hierarchy.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/components/Hierarchy/Cim10Hierarchy.tsx @@ -32,10 +32,10 @@ import { import useStyles from './styles' import { findSelectedInListAndSubItems } from 'utils/cohortCreation' import { decrementLoadingSyncHierarchyTable, incrementLoadingSyncHierarchyTable } from 'state/syncHierarchyTable' -import { defaultCondition } from '../../index' import { HierarchyTree } from 'types' -import { Cim10DataType } from 'types/requestCriterias' import { Hierarchy } from 'types/hierarchy' +import { Cim10DataType } from '../../../forms/Cim10Form' +import { CriteriaType } from 'types/requestCriterias' type CimListItemProps = { cim10Item: Hierarchy @@ -65,7 +65,7 @@ const CimListItem: React.FC = (props) => { cim10Code, selectedItems || [], cim10Hierarchy, - defaultCondition.type, + CriteriaType.CONDITION, dispatch ) await handleClick(selectedItems, newHierarchy) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/index.tsx index fadc12a81..945c1815e 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/index.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form/index.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react' import { Tab, Tabs } from '@mui/material' -import Cim10Form from './components/Form/Cim10Form' import Cim10Hierarchy from './components/Hierarchy/Cim10Hierarchy' import useStyles from './styles' @@ -10,30 +9,17 @@ import { useAppDispatch, useAppSelector } from 'state' import { fetchCondition } from 'state/pmsi' import { EXPLORATION } from '../../../../../../../../constants' import { CriteriaDrawerComponentProps } from 'types' -import { Cim10DataType, Comparators, CriteriaType } from 'types/requestCriterias' import { Hierarchy } from 'types/hierarchy' - -export const defaultCondition: Omit = { - type: CriteriaType.CONDITION, - title: 'Critère de diagnostic', - code: [], - source: 'AREM', - label: undefined, - diagnosticType: [], - occurrence: 1, - occurrenceComparator: Comparators.GREATER_OR_EQUAL, - startOccurrence: [null, null], - isInclusive: true, - encounterStartDate: [null, null], - encounterEndDate: [null, null], - encounterStatus: [] -} +import { Cim10DataType, form } from '../forms/Cim10Form' +import { CriteriaType } from 'types/requestCriterias' +import CriteriaForm from '../CriteriaForm' +import { fetchValueSet } from 'services/aphp/callApi' const Index = (props: CriteriaDrawerComponentProps) => { - const { criteriaData, selectedCriteria, onChangeSelectedCriteria, goBack } = props + const { selectedCriteria, onChangeSelectedCriteria, goBack } = props const [selectedTab, setSelectedTab] = useState<'form' | 'hierarchy'>(selectedCriteria ? 'form' : 'hierarchy') const [defaultCriteria, setDefaultCriteria] = useState( - (selectedCriteria as Cim10DataType) || defaultCondition + (selectedCriteria as Cim10DataType) || { ...form().initialData } ) const isEdition = selectedCriteria !== null @@ -55,7 +41,7 @@ const Index = (props: CriteriaDrawerComponentProps) => { newHierarchy, setDefaultCriteria, selectedTab, - defaultCondition.type, + CriteriaType.CONDITION, dispatch ) const _initSyncHierarchyTableEffect = async () => { @@ -64,7 +50,7 @@ const Index = (props: CriteriaDrawerComponentProps) => { selectedCriteria, defaultCriteria && defaultCriteria.code ? defaultCriteria.code : [], fetchCondition, - defaultCondition.type, + CriteriaType.CONDITION, dispatch ) } @@ -84,17 +70,21 @@ const Index = (props: CriteriaDrawerComponentProps) => { - { - + fetchValueSet( + codeSystemUrl, + { valueSetTitle: 'Toute la hiérarchie', search: code, noStar: false }, + abortSignal + ) + } /> - } + )} { = { + setError: (error?: string) => void + updateData: (data: T) => void + data: T + getValueSetOptions: CriteriaFormItemViewProps['getValueSetOptions'] + searchCode: CriteriaFormItemViewProps['searchCode'] + viewRenderers: { [key in CriteriaFormItemType]: CriteriaFormItemView } + deidentified: boolean +} + +type CritieraItemProps = CriteriaItemRuntimeProps & CriteriaItem + +export const renderLabel = (label: string, tooltip?: string, altStyle?: boolean) => { + if (altStyle) { + return ( + + Fin de prise en charge + {tooltip && ( + + + + )} + + ) + } + return label +} + +export const CFItem = >(props: CritieraItemProps) => { + const { valueKey, updateData, data, setError, getValueSetOptions, searchCode, viewRenderers } = props + const View = viewRenderers[props.type] as CriteriaFormItemView + const fieldValue = (valueKey ? data[valueKey] : undefined) as DataTypeMapping[U['type']]['dataType'] + const context = { deidentified: props.deidentified } + const displayCondition = props.displayCondition + console.log('rerendering', props.label, data) + if ( + displayCondition && + ((isFunction(displayCondition) && !displayCondition(data as Record, context)) || + (isString(displayCondition) && !eval(displayCondition)(data, context))) + ) { + return null + } + const disableCondition = props.disableCondition + const disabled = + disableCondition && + ((isFunction(disableCondition) && disableCondition(data as Record, context)) || + (isString(disableCondition) && eval(disableCondition)(data, context))) + return ( + valueKey && updateData({ ...data, [valueKey]: value })} + getValueSetOptions={getValueSetOptions} + searchCode={searchCode} + setError={setError} + deidentified={props.deidentified} + /> + ) +} + +export function CFSection( + props: PropsWithChildren, 'items'> & { collapsed?: boolean }> +) { + const { classes } = useStyles() + return props.title ? ( + + + {props.children} + + + ) : ( + <>{props.children} + ) +} + +export function CFItemWrapper( + props: PropsWithChildren<{ + label?: string | ((data: Record, context: Context) => string) + info?: string + data: T + context: Context + }> +) { + const { label, data, context } = props + const { classes } = useStyles() + const labelValue = + label && + ((isFunction(label) && label(data as Record, context)) || + (isString(label) && eval(label)(data, context))) + return ( + + {labelValue ? ( + + {props.info && ( + + + + )} + + ) : ( + '' + )} + {props.children} + + ) +} + +export default { CFItemWrapper, CFSection, renderLabel, CFItem } diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/criteriaform.stories.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/criteriaform.stories.tsx new file mode 100644 index 000000000..3c62f7b4c --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/criteriaform.stories.tsx @@ -0,0 +1,196 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import React from 'react' +import type { Meta, StoryObj } from '@storybook/react' + +import CriteriaForm from './index' +import { Provider } from 'react-redux' +import { store } from '../../../../../../../../state/store' +import { + Comparators, + ConditionParamsKeys, + CriteriaType, + ResourceType +} from '../../../../../../../../types/requestCriterias' +import { getConfig } from '../../../../../../../../config' +import { SourceType } from '../../../../../../../../types/scope' +import { fn } from '@storybook/test' + +const meta = { + title: 'CriteriaForm', + component: CriteriaForm, + decorators: [ + (Story) => ( + + + + ) + ], + tags: ['autodocs'] +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + goBack: fn(), + updateData: fn(), + label: 'Diagnostic', + initialData: { + type: CriteriaType.CONDITION, + title: 'Critère de diagnostic', + isInclusive: true, + occurrence: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + encounterService: null, + startOccurrence: null, + encounterStartDate: null, + encounterEndDate: null, + encounterStatus: [], + code: null, + source: 'AREM', + diagnosticType: null + }, + errorMessages: { + INCOHERENT_VALUE_ERROR: 'La valeur minimale ne peut pas être supérieure à la valeur maximale.', + INVALID_VALUE_ERROR: 'Veuillez entrer un nombre valide.', + MISSING_VALUE_ERROR: 'Veuillez entrer 2 valeurs avec ce comparateur.', + ADVANCED_INPUTS_ERROR: 'Erreur dans les options avancées.', + NO_ERROR: '' + }, + buildInfo: { + criteriaType: CriteriaType.CONDITION, + resourceType: ResourceType.CONDITION, + defaultFilter: 'subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence' as any, + type: 'numberAndComparator', + label: "Nombre d'occurrences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'source' as any, + type: 'radioChoice', + label: 'Source', + choices: [ + { id: 'AREM', label: 'AREM' }, + { id: 'ORBIS', label: 'ORBIS' } + ], + buildInfo: { + fhirKey: ConditionParamsKeys.SOURCE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Source: ' }] + } + }, + { + type: 'info', + content: 'Les données AREM sont disponibles uniquement pour la période du 07/12/2009 au 31/12/2022.', + contentType: 'warning' + }, + { + type: 'info', + content: + 'Seuls les diagnostics rattachés à une visite Orbis (avec un Dossier Administratif - NDA) sont actuellement disponibles.', + contentType: 'warning' + }, + { + type: 'info', + content: + "Les données PMSI d'ORBIS sont codées au quotidien par les médecins. Les données PMSI AREM sont validées, remontées aux tutelles et disponibles dans le SNDS.", + contentType: 'info' + }, + { + valueKey: 'code' as any, + type: 'codeSearch', + valueSetIds: [getConfig().features.condition.valueSets.conditionHierarchy.url], + noOptionsText: 'Veuillez entrer un code ou un diagnostic CIM10', + label: 'Code CIM10', + buildInfo: { + fhirKey: ConditionParamsKeys.CODE, + buildMethodExtraArgs: [ + { type: 'string', value: getConfig().features.condition.valueSets.conditionHierarchy.url } + ] + } + }, + { + valueKey: 'diagnosticType' as any, + type: 'autocomplete', + label: 'Type de diagnostic', + valueSetId: getConfig().features.condition.valueSets.conditionStatus.url, + noOptionsText: 'Veuillez entrer un type de diagnostic', + buildInfo: { + fhirKey: ConditionParamsKeys.DIAGNOSTIC_TYPES + } + }, + { + valueKey: 'encounterStatus' as any, + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée', + buildInfo: { + fhirKey: ConditionParamsKeys.ENCOUNTER_STATUS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite associée' }] + } + } + ] + }, + { + title: 'Options avancées', + defaulCollapsed: true, + items: [ + { + valueKey: 'encounterService', + label: 'Unité exécutrice', + type: 'executiveUnit', + sourceType: SourceType.CIM10, + buildInfo: { + fhirKey: ConditionParamsKeys.EXECUTIVE_UNITS + } + }, + { + valueKey: 'encounterStartDate' as any, + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + label: 'Début de prise en charge', + labelAltStyle: true, + extraLabel: () => 'Prise en charge', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-start', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de début de prise en charge' }] + } + }, + { + valueKey: 'encounterEndDate' as any, + type: 'calendarRange', + label: 'Fin de prise en charge', + labelAltStyle: true, + errorType: 'ADVANCED_INPUTS_ERROR', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-end', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de fin de prise en charge' }] + } + }, + { + valueKey: 'startOccurrence' as any, + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + extraLabel: () => 'Date du diagnostic CIM10', + buildInfo: { + fhirKey: ConditionParamsKeys.DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date du diagnostic CIM10' }] + } + } + ] + } + ] + } as any +} diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/index.tsx new file mode 100644 index 000000000..1685c963f --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/index.tsx @@ -0,0 +1,112 @@ +import React, { useEffect, useState } from 'react' +import CriteriaLayout from 'components/ui/CriteriaLayout' +import { + CriteriaForm as CriteriaFormDefinition, + CriteriaFormItemViewProps, + NumberAndComparatorDataType, + NewDurationRangeType, + CommonCriteriaData +} from './types' +import { CFItem, CFItemWrapper, CFSection } from './components' +import FORM_ITEM_RENDERER from './mappers/renderers' +import { useAppSelector } from 'state' +import { LabelObject } from 'types/searchCriterias' + +export type CriteriaFormRuntimeProps = { + goBack: () => void + data?: T + updateData: (data: T) => void + onDataChange?: (data: T) => void + searchCode: CriteriaFormItemViewProps['searchCode'] +} + +type CriteriaFormProps = CriteriaFormDefinition & CriteriaFormRuntimeProps + +export default function CriteriaForm(props: CriteriaFormProps) { + const [criteriaData, setCriteriaData] = useState(props.data ?? (props.initialData as T)) + const valueSets = useAppSelector((state) => state.valueSets) + const selectedPopulation = useAppSelector((state) => state.cohortCreation.request.selectedPopulation || []) + const { goBack, updateData, label, warningAlert, itemSections, errorMessages, onDataChange } = props + const isEdition = !!props.data + const [error, setError] = useState() + + const deidentified: boolean = + selectedPopulation !== null && + selectedPopulation + .map((population) => population && population.access) + .filter((elem) => elem && elem === 'Pseudonymisé').length > 0 + + useEffect(() => { + onDataChange?.(criteriaData) + }, [criteriaData, onDataChange]) + + console.log('rendering form') + + return ( + setCriteriaData({ ...criteriaData, title })} + isEdition={isEdition} + goBack={goBack} + onSubmit={() => updateData(criteriaData)} + disabled={error !== undefined} + isInclusive={!!criteriaData.isInclusive} + onChangeIsInclusive={(isInclusive) => setCriteriaData({ ...criteriaData, isInclusive: isInclusive })} + infoAlert={['Tous les éléments des champs multiples sont liés par une contrainte OU']} + warningAlert={warningAlert} + errorAlert={error ? [errorMessages[error]] : undefined} + > + {itemSections.map((section, index) => ( + { + if (item.valueKey) { + const value = criteriaData[item.valueKey] + let isNull = !value || (Array.isArray(value) && value.length === 0) + if (item.type === 'numberAndComparator') { + isNull = isNull || !(value as NumberAndComparatorDataType).value + } else if (item.type === 'calendarRange' || item.type === 'durationRange') { + const duration = value as NewDurationRangeType + isNull = isNull || (duration.start === null && duration.end === null) + } + if (!isNull) console.log(isNull, item.valueKey, value) + + return isNull + } + return true + })} + > + {section.items.map((item, index) => ( + + { + return (valueSets.entities[valueSetId]?.options || []) as LabelObject[] + }, + searchCode: props.searchCode, + updateData: (newData: T) => { + setCriteriaData({ ...criteriaData, ...newData }) + }, + deidentified, + setError + }} + /> + + ))} + + ))} + + ) +} diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/buildMappers.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/buildMappers.ts new file mode 100644 index 000000000..ce31990fa --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/buildMappers.ts @@ -0,0 +1,253 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { ScopeElement } from 'types' +import { DocumentAttachmentMethod, LabelObject } from 'types/searchCriterias' +import { + convertDurationToString, + convertDurationToTimestamp, + convertStringToDuration, + convertTimestampToDuration +} from 'utils/age' +import moment from 'moment' +import { Comparators } from 'types/requestCriterias' +import { comparatorToFilter, parseOccurence } from 'utils/valueComparator' +import services from 'services/aphp' +import { Hierarchy } from 'types/hierarchy' +import { DataTypes, FhirKey, NewDurationRangeType, NumberAndComparatorDataType } from '../types' + +/************************************************************************************/ +/* Criteria Form Item Mappers */ +/************************************************************************************/ +/* +This file contains the list of functions used to build and unbuild criteria data from a fhir filter. +Each CriteriaFormItemType and its corresponding DataType should be handled here +The default for each CriteriaFormItemType are specified in the main criteria data mapper file (index.ts) +*/ + +export type BuilderMethod = ( + arg: DataTypes, + fhirKey: FhirKey, + deidentified: boolean, + args: Array +) => string | string[] | undefined | { filterValue: string; filterKey: string } + +const searchReducer = (accumulator: string, currentValue: string): string => { + if (currentValue === '') { + return accumulator + } + return !!accumulator ? `${accumulator},${currentValue}` : currentValue || accumulator +} + +const COMPARATORS_REGEX = /(le|ge)/gi + +const replaceTime = (date?: string) => { + return date?.replace('T00:00:00Z', '') ?? null +} + +const buildLabelObjectFilter = ( + criterion: LabelObject[] | undefined | null, + hierarchyUrl?: string, + system?: boolean +) => { + if (criterion && criterion.length > 0) { + const filter = criterion.find((code) => code.id === '*') + ? `${hierarchyUrl}|*` + : `${criterion + .map((item) => + system && (item.system || hierarchyUrl) ? `${item.system ?? hierarchyUrl}|${item.id}` : item.id + ) + .reduce(searchReducer, '')}` + return filter + } + return '' +} + +const unbuildLabelObjectFilterValue = (values: string | null, prevValues: LabelObject[] | null) => { + const valuesIds = values?.split(',') || [] + const newArray = valuesIds?.map((value) => (value.includes('|*') ? { id: '*', label: '' } : { id: value, label: '' })) + if (newArray) { + return [...(prevValues || []), ...newArray] + } + return prevValues +} + +const buildEncounterServiceFilter = (criterion?: Hierarchy[] | null) => { + return criterion && criterion.length > 0 ? `${criterion.map((item) => item.id).reduce(searchReducer, '')}` : '' +} + +const unbuildEncounterServiceFilter = async ( + value: string | null, + existingValue?: Hierarchy[] +) => { + if (value) { + const encounterServices: ScopeElement[] = (await services.perimeters.getPerimeters({ ids: value })).results + return encounterServices.concat(existingValue || []) + } + return Promise.resolve(existingValue || []) +} + +const buildDateFilter = ( + dateRange: NewDurationRangeType | null, + fhirKey: FhirKey, + removeTimeZone = false, + whithSpace = false +): string[] | undefined | { filterValue: string; filterKey: string } => { + const fhirKeyString = typeof fhirKey === 'string' ? fhirKey : 'id' in fhirKey ? fhirKey.id : fhirKey.main + if (dateRange?.includeNull && (dateRange?.start || dateRange?.end)) { + let dateFilter = '' + if (dateRange.start && dateRange.end) { + dateFilter = `${fhirKeyString} ${buildDateFilterValue( + dateRange.start, + 'ge', + false, + true + )} and ${fhirKeyString} ${buildDateFilterValue(dateRange.end, 'le', false, true)}` + } else { + dateFilter = `${fhirKeyString} ${ + dateRange.start + ? buildDateFilterValue(dateRange.start, 'ge', false, true) + : buildDateFilterValue(dateRange.end, 'le', false, true) + }` + } + return { filterKey: '_filter', filterValue: `(${dateFilter}) or not (${fhirKeyString} eq "*")` } + } else { + const filterValues = [] + if (dateRange?.start) { + filterValues.push(buildDateFilterValue(dateRange.start, 'ge', removeTimeZone, whithSpace)) + } + if (dateRange?.end) { + filterValues.push(buildDateFilterValue(dateRange.end, 'le', removeTimeZone, whithSpace)) + } + return filterValues + } +} + +const buildDateFilterValue = ( + criterion: string | null | undefined, + comparator: 'le' | 'ge', + removeTimeZone = false, + withSpace = false +) => { + const _withSpace = withSpace ? ' ' : '' + const dateFormat = `YYYY-MM-DD[T00:00:00${removeTimeZone ? '' : 'Z'}]` + + return criterion ? `${comparator}${_withSpace}${moment(criterion).format(dateFormat)}` : '' +} + +const unbuildDateFilter = (value: string, existingValue?: NewDurationRangeType) => { + const date = replaceTime(value.replace(COMPARATORS_REGEX, '')) + const updatedValue = existingValue || { start: null, end: null, includeNull: false } + if (value.includes('ge')) { + updatedValue.start = date + } else if (value.includes('le')) { + updatedValue.end = date + } else if (value.includes('not*')) { + updatedValue.includeNull = true + } + return updatedValue +} + +const buildDurationFilters = (duration: NewDurationRangeType | null, deidentified?: boolean) => { + const filterValues = [] + const convertedRange = convertDurationToTimestamp(convertStringToDuration(duration?.start), deidentified) + if (convertedRange !== null) { + filterValues.push(`ge${convertedRange}`) + } + const convertedRangeEnd = convertDurationToTimestamp(convertStringToDuration(duration?.end), deidentified) + if (convertedRangeEnd !== null) { + filterValues.push(`le${convertedRangeEnd}`) + } + return filterValues +} + +const unbuildDurationFilter = (value: string, deid: boolean, existingValue?: NewDurationRangeType) => { + const cleanValue = value?.replace(COMPARATORS_REGEX, '') + const duration = convertDurationToString(convertTimestampToDuration(+cleanValue, deid)) + const updatedValue = existingValue || { start: null, end: null, includeNull: false } + if (value.includes('ge')) { + updatedValue.start = duration + } else if (value.includes('le')) { + updatedValue.end = duration + } + return updatedValue +} + +const buildSearchFilter = (criterion: string) => { + return criterion ? `${encodeURIComponent(criterion)}` : '' +} + +const unbuildSearchFilter = (value: string | null) => { + return value !== null ? decodeURIComponent(value) : '' +} + +const buildNumberComparatorFilter = ( + numberAndComparator: NumberAndComparatorDataType | null, + defaultValue?: string +) => { + if (!numberAndComparator) { + return defaultValue ?? '' + } + if (numberAndComparator.comparator === Comparators.BETWEEN) { + if (numberAndComparator.maxValue) { + return `ge${numberAndComparator.value}&le${numberAndComparator.maxValue}` + } else { + console.error('Missing max value for between comparator') + return `ge${numberAndComparator.value}` + } + } + return `${comparatorToFilter(numberAndComparator.comparator)}${numberAndComparator.value}` +} + +const buildWithDocumentFilter = (withDocument: LabelObject[], daysOfDelay: number | null) => { + if (withDocument && withDocument.length === 1 && withDocument.at(0)?.id !== DocumentAttachmentMethod.NONE) { + return `${ + withDocument.at(0)?.id === DocumentAttachmentMethod.ACCESS_NUMBER + ? DocumentAttachmentMethod.ACCESS_NUMBER + : `INFERENCE_TEMPOREL${daysOfDelay ? `_${daysOfDelay}_J` : ''}` + }` + } + return '' +} + +/********************************************************************************************* */ +/* Item mapper list */ +/********************************************************************************************* */ + +export const UNBUILD_MAPPERS = { + unbuildLabelObject: async (val: string, deid: boolean, existingValue?: DataTypes) => + Promise.resolve(unbuildLabelObjectFilterValue(val, existingValue as LabelObject[])), + unbuildEncounterService: async (val: string, deid: boolean, existingValue?: DataTypes) => + unbuildEncounterServiceFilter(val, existingValue as Hierarchy[]), + unbuildDate: async (val: string, deid: boolean, existingValue?: DataTypes) => + Promise.resolve(unbuildDateFilter(val, existingValue as NewDurationRangeType)), + unbuildDuration: async (val: string, deid: boolean, existingValue?: DataTypes) => + Promise.resolve(unbuildDurationFilter(val, deid, existingValue as NewDurationRangeType)), + unbuildSearch: async (val: string, deid: boolean, existingValue?: DataTypes) => + Promise.resolve(unbuildSearchFilter(val)), + unbuildComparator: async (val: string, deid: boolean, existingValue?: DataTypes) => + Promise.resolve(parseOccurence(val)) +} + +export const BUILD_MAPPERS = { + buildLabelObject: (val: DataTypes, key: FhirKey, deidentified: boolean, args: Array) => + buildLabelObjectFilter(val as LabelObject[], args[0] as string, args[1] as boolean), + buildEncounterService: ( + val: DataTypes, + key: FhirKey, + deidentified: boolean, + args: Array + ) => buildEncounterServiceFilter(val as Hierarchy[]), + buildDate: (val: DataTypes, key: FhirKey, deidentified: boolean, args: Array) => + buildDateFilter(val as NewDurationRangeType, key, args[0] as boolean, args[1] as boolean), + buildDuration: (val: DataTypes, key: FhirKey, deidentified: boolean, args: Array) => + buildDurationFilters(val as NewDurationRangeType, deidentified), + buildSearch: (val: DataTypes, key: FhirKey, deidentified: boolean, args: Array) => + buildSearchFilter(val as string), + buildComparator: (val: DataTypes, key: FhirKey, deidentified: boolean, args: Array) => + buildNumberComparatorFilter(val as NumberAndComparatorDataType, args[0] as string), + buildWithDocument: (val: DataTypes, key: FhirKey, deidentified: boolean, args: Array) => + buildWithDocumentFilter(val as LabelObject[], args[0] as number | null), + // utility meta functions + skipIf: (val: DataTypes, key: FhirKey, deidentified: boolean, args: Array) => { + return args[1] === args[2] ? undefined : (args[0] as BuilderMethod)(val, key, deidentified, args.slice(3)) + } +} diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/chipDisplayMapper.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/chipDisplayMapper.tsx new file mode 100644 index 000000000..dbd9b4af9 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/chipDisplayMapper.tsx @@ -0,0 +1,302 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React from 'react' +import { ValueSetStore } from 'state/valueSets' +import { + AutoCompleteItem, + CodeSearchItem, + DataTypes, + GenericCriteriaItem, + NewDurationRangeType, + NumberAndComparatorDataType +} from '../types' +import { ReactNode } from 'react' +import { + DocumentAttachmentMethod, + DocumentAttachmentMethodLabel, + LabelObject, + SearchByTypes +} from 'types/searchCriterias' +import { Hierarchy } from 'types/hierarchy' +import { ScopeElement } from 'types' +import { Tooltip, Typography } from '@mui/material' +import { Comparators } from 'types/requestCriterias' +import allDocTypes from 'assets/docTypes.json' +import moment from 'moment' +import { getDurationRangeLabel } from 'utils/age' +import { getConfig } from 'config' + +/************************************************************************************/ +/* Criteria Form Item Chip Display */ +/************************************************************************************/ +/* +This file contains the list of functions used to display a criteria form item and its corresponding data type into a ReactNode (a chip for now). +Each CriteriaFormItemType and its corresponding DataType should be handled here +The default for each CriteriaFormItemType are specified in the main criteria data mapper file (index.ts) +*/ + +export type ChipDisplayMethod = ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array +) => ReactNode | undefined + +const chipForScopeElement = (values: Hierarchy[] | null) => { + if (!values) return null + const labels = values.map((value) => `${value.source_value} - ${value.name}`).join(' - ') + return labels +} + +const chipForDateLabel = (dateRange: NewDurationRangeType, word?: string) => { + const excludeNullDatesWording = dateRange.includeNull ? ', valeurs nulles incluses' : '' + let datesLabel = '' + if (dateRange.start && dateRange.end) { + datesLabel = `${word ? word + ' entre' : 'Entre'} le ${moment(dateRange.start).format('DD/MM/YYYY')} et le ${moment( + dateRange.end + ).format('DD/MM/YYYY')}${excludeNullDatesWording}` + } + if (dateRange.start && !dateRange.end) { + datesLabel = `${word ? word + ' à' : 'À'} partir du ${moment(dateRange.start).format( + 'DD/MM/YYYY' + )}${excludeNullDatesWording}` + } + if (!dateRange.start && dateRange.end) { + datesLabel = `${word ? word + " jusqu'au" : "jusqu'au"} ${moment(dateRange.end).format( + 'DD/MM/YYYY' + )}${excludeNullDatesWording}` + } + + return ( + + {datesLabel} + + ) +} + +const getSearchDocumentLabel = (value: string, searchBy: string) => { + const loc = searchBy === SearchByTypes.TEXT ? 'document' : 'titre du document' + return `Contient "${value}" dans le ${loc}` +} + +const getDocumentTypesLabel = (values: LabelObject[]) => { + const allTypes = new Set(allDocTypes.docTypes.map((docType) => docType.type)) + + const displayingSelectedDocTypes = values.reduce((acc, selectedDocType) => { + const numberOfElementFromGroup = selectedDocType.type && allTypes.has(selectedDocType.type) ? allTypes.size : 0 + const numberOfElementSelected = values.filter((doc) => doc.type === selectedDocType.type).length + + if (numberOfElementFromGroup === numberOfElementSelected) { + return acc + } else { + return [...acc, selectedDocType] + } + }, [] as LabelObject[]) + + const currentDocTypes = displayingSelectedDocTypes.map(({ label }) => label).join(' - ') + + return currentDocTypes +} + +const chipForNumberAndComparator = (value: NumberAndComparatorDataType, name: string) => { + if (value.comparator === Comparators.BETWEEN) { + return `${name} entre ${value.value} et ${!value.maxValue ? '?' : value.maxValue}` + } + return `${name} ${value.comparator} ${+value.value}` +} +const getDocumentStatusLabel = (value: LabelObject[]) => { + return `Statut de documents : ${value.map((l) => l.id).join(', ')}` +} + +const getIdsListLabels = (values: string, name: string) => { + const labels = values.split(',').join(' - ') + return `Contient les ${name} : ${labels}` +} + +const getAttachmentMethod = (value: DocumentAttachmentMethod, daysOfDelay: string | null) => { + if (value === DocumentAttachmentMethod.INFERENCE_TEMPOREL) { + return `Rattachement aux documents par ${DocumentAttachmentMethodLabel.INFERENCE_TEMPOREL.toLocaleLowerCase()}${ + daysOfDelay !== '' && daysOfDelay !== null ? ` de ${daysOfDelay} jour(s)` : '' + }` + } else if (value === DocumentAttachmentMethod.ACCESS_NUMBER) { + return `Rattachement aux documents par ${DocumentAttachmentMethodLabel.ACCESS_NUMBER.toLocaleLowerCase()}` + } else { + return '' + } +} + +const getLabelsForCodeSearchItem = ( + val: LabelObject[], + item: CodeSearchItem, + valueSets: ValueSetStore +): LabelObject[] => { + return val + .map((value) => { + return ( + (value.system ? valueSets.cache[value.system] : item.valueSetIds.flatMap((vid) => valueSets.cache[vid])) || [] + ).find((code) => code.id === value.id) as LabelObject + }) + .filter((code) => code !== undefined) +} + +const getLabelsForAutoCompleteItem = ( + val: LabelObject[], + item: AutoCompleteItem, + valueSets: ValueSetStore +): LabelObject[] => { + return val + .map((value) => { + return (item.valueSetData || valueSets.entities[item.valueSetId]?.options || []).find( + (code) => code.id === value.id + ) + }) + .filter((code) => code !== undefined) +} + +const chipFromAutoComplete = ( + val: LabelObject[] | null, + item: AutoCompleteItem, + valueSets: ValueSetStore, + label?: string +) => { + return chipFromLabelObject(val, item, getLabelsForAutoCompleteItem, valueSets, label) +} + +const chipFromCodeSearch = ( + val: LabelObject[] | null, + item: CodeSearchItem, + valueSets: ValueSetStore, + label?: string +) => { + return chipFromLabelObject(val, item, getLabelsForCodeSearchItem, valueSets, label) +} + +// TODO refacto this to be more generic using config +const displaySystem = (system?: string) => { + switch (system) { + case getConfig().features.medication.valueSets.medicationAtc.url: + return `${getConfig().features.medication.valueSets.medicationAtc.title}: '` + case getConfig().features.medication.valueSets.medicationUcd.url: + return `${getConfig().features.medication.valueSets.medicationUcd.title}: '` + default: + return '' + } +} + +const chipFromLabelObject = ( + val: LabelObject[] | null, + item: T, + getLabel: (val: LabelObject[], item: T, valueSets: ValueSetStore) => LabelObject[], + valueSets: ValueSetStore, + label?: string +) => { + console.log('val', val) + if (val === null) return null + const labels = getLabel(val, item, valueSets).map((code) => `${displaySystem(code.system)} ${code.label}`) + console.log('label', labels) + const tooltipTitle = labels.join(' - ') + return ( + + + {label} {tooltipTitle} + + + ) +} + +export const CHIPS_DISPLAY_METHODS = { + calendarRange: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => chipForDateLabel(val as NewDurationRangeType, args[0] as string), + durationRange: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => { + const typedVal = val as NewDurationRangeType + return getDurationRangeLabel([typedVal.start, typedVal.end], args[0] as string) + }, + raw: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => `${args[0] ? args[0] : ''}${val as string}`, + autocomplete: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => chipFromAutoComplete(val as LabelObject[], item as AutoCompleteItem, valueSets, args[0] as string), + executiveUnit: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => chipForScopeElement(val as Hierarchy[]), + numberAndComparator: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => chipForNumberAndComparator(val as NumberAndComparatorDataType, args[0] as string), + codeSearch: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => chipFromCodeSearch(val as LabelObject[], item as CodeSearchItem, valueSets, args[0] as string), + idListLabel: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => getIdsListLabels(val as string, args[0] as string), + getAttachmentMethod: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => getAttachmentMethod(val as DocumentAttachmentMethod, args[0] as string), + getSearchDocumentLabel: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => getSearchDocumentLabel(val as string, args[0] as string), + getDocumentTypesLabel: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => getDocumentTypesLabel(val as LabelObject[]), + getDocumentStatusLabel: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => getDocumentStatusLabel(val as LabelObject[]), + altArgs: ( + val: DataTypes, + item: GenericCriteriaItem, + valueSets: ValueSetStore, + args: Array + ) => { + return args[1] === args[2] + ? (args[0] as ChipDisplayMethod)( + val, + item, + valueSets, + args.slice(3).filter((arg, i) => i % 2 === 0) + ) + : (args[0] as ChipDisplayMethod)( + val, + item, + valueSets, + args.slice(3).filter((arg, i) => i % 2 === 1) + ) + } +} diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/index.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/index.ts new file mode 100644 index 000000000..57097202e --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/index.ts @@ -0,0 +1,317 @@ +import { Comparators, CriteriaType, RequeteurCriteriaType, SelectedCriteriaType } from 'types/requestCriterias' +import { BuildMethodExtraParam, CriteriaForm, CriteriaFormItemType, DataTypes, FhirKey } from '../types' +import { ChipDisplayMethod, CHIPS_DISPLAY_METHODS } from './chipDisplayMapper' +import extractFilterParams, { FhirFilter } from 'utils/fhirFilterParser' +import { CriteriaItemType } from 'types' +import { ValueSetStore } from 'state/valueSets' +import { ReactNode } from 'react' +import { isArray, isString } from 'lodash' +import { BUILD_MAPPERS, BuilderMethod, UNBUILD_MAPPERS } from './buildMappers' + +/************************************************************************************/ +/* Criteria Form Data Mappers */ +/************************************************************************************/ +/* +This file contains 3 main functions: +- constructFhirFilterForType: This function is used to construct FHIR filters for a given criteria type +- unbuildCriteriaDataFromDefinition: This function is used to unbuild criteria data from a fhir filter +- criteriasAsArray: This function is used to convert criteria data to an array of ReactNode (chips to be displayed) +*/ + +const LINK_ID_PARAM_NAME = 'item.linkId' +const VALUE_PARAM_NAME_PREFIX = 'item.answer.' +const FILTER_PARAM_NAME = '_filter' + +function parseExtraArgs( + extraArg: BuildMethodExtraParam, + obj: SelectedCriteriaType, + methods: Record +): DataTypes | T { + if (extraArg.type === 'reference') { + return obj[extraArg.value as keyof SelectedCriteriaType] || null + } else if (extraArg.type === 'method') { + return methods[extraArg.value as keyof typeof methods] + } + return extraArg.value +} + +const parseFhirKey = ( + fhirKey: Exclude, + deidentified: boolean, + obj: SelectedCriteriaType +): string => { + if (isString(fhirKey)) { + return fhirKey + } + if ('deid' in fhirKey) { + return deidentified ? fhirKey.deid : fhirKey.main + } + return parseExtraArgs(fhirKey.value1, obj, BUILD_MAPPERS) === parseExtraArgs(fhirKey.value2, obj, BUILD_MAPPERS) + ? fhirKey.main + : fhirKey.alt +} + +const buildFilter = (fhirKey: string, value?: string | { filterValue: string; filterKey: string } | null): string => { + if (!value) { + return '' + } + if (isString(value)) { + return `${fhirKey}=${value}` + } + return `${value.filterKey}=${value.filterValue}` +} + +const quoteValue = (value: string, type: string) => { + return ['valueString', 'valueCoding'].includes(type) ? `"${value}"` : value +} + +const questionnaireFiltersBuilders = ( + fhirKey: { id: string; type: string }, + value?: + | string + | { + filterValue: string + filterKey: string + } +) => { + const strValue = isString(value) ? value : value?.filterValue + const slice = strValue?.slice(0, 2) + const operator = slice === 'ge' || slice === 'le' || slice === 'lt' || slice === 'gt' || slice === 'eq' ? slice : 'eq' + const _value = slice === 'ge' || slice === 'le' || slice === 'lt' || slice === 'gt' ? strValue?.slice(2) : strValue + + if (fhirKey.type === 'valueBoolean' || fhirKey.type === 'valueCoding') { + const _code = strValue?.split(',') + return strValue && _code && _code?.length > 0 + ? `${FILTER_PARAM_NAME}=${LINK_ID_PARAM_NAME} eq ${fhirKey.id} and (${_code + .map((code) => `${VALUE_PARAM_NAME_PREFIX}${fhirKey.type} eq ${quoteValue(code, fhirKey.type)}`) + .join(' or ')})` + : '' + } else { + return _value + ? `${FILTER_PARAM_NAME}=${LINK_ID_PARAM_NAME} eq ${fhirKey.id} and ${VALUE_PARAM_NAME_PREFIX}${ + fhirKey.type + } ${operator} ${quoteValue(_value, fhirKey.type)}` + : '' + } +} + +const matchFhirKey = (key: string, fhirKey?: FhirKey): boolean => { + if (!fhirKey) { + return false + } + if (isString(fhirKey)) { + return fhirKey === key + } + if ('main' in fhirKey) { + if ('deid' in fhirKey) return fhirKey.main === key || fhirKey.deid === key + return fhirKey.main === key || fhirKey.alt === key + } + return fhirKey.id === key +} + +const extractQuestionnaireFilterParams = (filterElements: Array): FhirFilter[] | undefined => { + const paramKey = filterElements.find((element) => element.param === LINK_ID_PARAM_NAME) + const paramValues = filterElements.filter((element) => element.param.startsWith(VALUE_PARAM_NAME_PREFIX)) + if (!paramKey || !paramValues) { + return undefined + } + const key = paramKey?.values[0].value + return [ + { + param: key, + values: paramValues.flatMap((element) => element.values) + } + ] +} + +const DEFAULT_BUILD_METHOD: Record = { + calendarRange: BUILD_MAPPERS.buildDate, + durationRange: BUILD_MAPPERS.buildDuration, + text: BUILD_MAPPERS.buildSearch, + autocomplete: BUILD_MAPPERS.buildLabelObject, + number: BUILD_MAPPERS.buildSearch, + executiveUnit: BUILD_MAPPERS.buildLabelObject, + numberAndComparator: BUILD_MAPPERS.buildComparator, + boolean: BUILD_MAPPERS.buildSearch, + textWithCheck: BUILD_MAPPERS.buildSearch, + codeSearch: BUILD_MAPPERS.buildLabelObject, + textWithRegex: BUILD_MAPPERS.buildSearch, + radioChoice: BUILD_MAPPERS.buildSearch, + info: () => undefined +} + +const DEFAULT_UNBUILD_METHOD: Record< + CriteriaFormItemType, + (arg: string, deidentified: boolean, existingValue?: DataTypes) => Promise +> = { + calendarRange: UNBUILD_MAPPERS.unbuildDate, + durationRange: UNBUILD_MAPPERS.unbuildDuration, + text: UNBUILD_MAPPERS.unbuildSearch, + autocomplete: UNBUILD_MAPPERS.unbuildLabelObject, + number: UNBUILD_MAPPERS.unbuildSearch, + executiveUnit: UNBUILD_MAPPERS.unbuildEncounterService, + numberAndComparator: UNBUILD_MAPPERS.unbuildComparator, + boolean: UNBUILD_MAPPERS.unbuildSearch, + textWithCheck: UNBUILD_MAPPERS.unbuildSearch, + codeSearch: UNBUILD_MAPPERS.unbuildLabelObject, + textWithRegex: UNBUILD_MAPPERS.unbuildSearch, + radioChoice: UNBUILD_MAPPERS.unbuildSearch, + info: async () => null +} + +const DEFAULT_CHIPS_DISPLAY_METHODS: Record = { + calendarRange: CHIPS_DISPLAY_METHODS.calendarRange, + durationRange: CHIPS_DISPLAY_METHODS.durationRange, + text: CHIPS_DISPLAY_METHODS.raw, + autocomplete: CHIPS_DISPLAY_METHODS.autocomplete, + number: CHIPS_DISPLAY_METHODS.raw, + executiveUnit: CHIPS_DISPLAY_METHODS.executiveUnit, + numberAndComparator: CHIPS_DISPLAY_METHODS.numberAndComparator, + boolean: CHIPS_DISPLAY_METHODS.raw, + textWithCheck: CHIPS_DISPLAY_METHODS.raw, + codeSearch: CHIPS_DISPLAY_METHODS.codeSearch, + textWithRegex: CHIPS_DISPLAY_METHODS.raw, + radioChoice: CHIPS_DISPLAY_METHODS.raw, + info: () => null +} + +export const constructFhirFilterForType = ( + criteria: T, + deidentified: boolean, + criteriaForm: CriteriaForm +): string => { + return criteriaForm.itemSections + .flatMap((section) => section.items) + .filter((item) => !!item.valueKey) + .map((item) => { + const buildInfo = item.buildInfo + if (buildInfo?.fhirKey === undefined || item.valueKey === undefined) { + return '' + } + const fhirKey = + isString(buildInfo.fhirKey) || 'main' in buildInfo.fhirKey + ? parseFhirKey(buildInfo.fhirKey, deidentified, criteria) + : buildInfo.fhirKey + const buildMethod = buildInfo.buildMethod ? BUILD_MAPPERS[buildInfo.buildMethod] : DEFAULT_BUILD_METHOD[item.type] + const value = criteria[item.valueKey] as DataTypes + const filterValues = buildMethod( + value, + fhirKey, + deidentified, + (buildInfo.buildMethodExtraArgs || []).map((arg) => parseExtraArgs(arg, criteria, BUILD_MAPPERS)) + ) + if (filterValues === undefined) { + return '' + } + + const filterValuesArray = Array.isArray(filterValues) ? filterValues : [filterValues] + return filterValuesArray + .map((val) => (isString(fhirKey) ? buildFilter(fhirKey, val) : questionnaireFiltersBuilders(fhirKey, val))) + .join('&') + }) + .filter((elem) => !!elem) + .reduce( + (accumulator: string, currentValue: string): string => + accumulator ? `${accumulator}&${currentValue}` : currentValue, + criteriaForm.buildInfo?.defaultFilter ?? '' + ) +} + +export const unbuildCriteriaDataFromDefinition = async ( + element: RequeteurCriteriaType, + type: CriteriaType, + criteriaDefinition: CriteriaForm +): Promise => { + const emptyCriterion: T = { ...criteriaDefinition.initialData } as T + emptyCriterion.id = element._id + emptyCriterion.type = type as SelectedCriteriaType['type'] + emptyCriterion.title = element.name + emptyCriterion.isInclusive = element.isInclusive + + if (element.occurrence && 'occurrence' in emptyCriterion) { + emptyCriterion.occurrence = { + value: element.occurrence.n ?? 1, + comparator: element.occurrence.operator ?? Comparators.GREATER_OR_EQUAL + } + } + + const criteriaItems = criteriaDefinition.itemSections.flatMap((section) => section.items) + if (element.filterFhir) { + const filters = element.filterFhir.split('&').map((elem) => elem.split('=')) + try { + const allFilters = filters.flatMap((filter) => { + if (filter[0] === FILTER_PARAM_NAME) { + const filterContent = filter[1] + const extractedFilterElements = extractFilterParams(filterContent, { omitOperatorEq: true }) + if (extractedFilterElements) { + return (extractQuestionnaireFilterParams(extractedFilterElements) || extractedFilterElements).map( + (filterParam) => { + return [ + filterParam.param, + filterParam.values.map((val) => `${val.operator ?? ''}${val.value}`).join(',') + ] + } + ) + } + return [] + } + return [filter] + }) + for (const filter of allFilters) { + const key = filter[0] ?? null + const value = filter[1] ?? null + if (key !== null) { + const item = criteriaItems.find((item) => matchFhirKey(key, item.buildInfo?.fhirKey)) + if (item?.buildInfo?.fhirKey && item?.valueKey) { + const unbuildMethod = item.buildInfo.unbuildMethod + ? UNBUILD_MAPPERS[item.buildInfo.unbuildMethod] + : DEFAULT_UNBUILD_METHOD[item.type] + const dataValue = await unbuildMethod( + value, + !isString(item.buildInfo.fhirKey) && + 'deid' in item.buildInfo.fhirKey && + item.buildInfo?.fhirKey.deid === key, + emptyCriterion[item?.valueKey] as DataTypes + ) + emptyCriterion[item?.valueKey] = dataValue as T[keyof T] + } + } + } + } catch (error) { + console.error(error) + emptyCriterion.error = true + } + } + return emptyCriterion +} + +export const criteriasAsArray = ( + selectedCriteria: SelectedCriteriaType, + criteriaDefinitions: CriteriaItemType[], + valuesets: ValueSetStore +): ReactNode[] => { + const criteriaDef = criteriaDefinitions.find((criterion) => + isString(criterion.formDefinition?.buildInfo?.criteriaType) + ? criterion.formDefinition?.buildInfo?.criteriaType === selectedCriteria.type + : criterion.formDefinition?.buildInfo?.criteriaType.includes(selectedCriteria.type) + )?.formDefinition + if (!criteriaDef) return [] + + const chips = criteriaDef.itemSections + .flatMap((section) => section.items) + .map((item) => { + if (!item.buildInfo || !item.valueKey) return null + const val = selectedCriteria[item.valueKey as keyof typeof selectedCriteria] + if (!val || (isArray(val) && val.length === 0)) return null + const chipBuilder = item.buildInfo?.chipDisplayMethod + ? CHIPS_DISPLAY_METHODS[item.buildInfo?.chipDisplayMethod] + : DEFAULT_CHIPS_DISPLAY_METHODS[item.type] + const extraArgs = (item.buildInfo?.chipDisplayMethodExtraArgs || []).map((arg) => + parseExtraArgs(arg, selectedCriteria, CHIPS_DISPLAY_METHODS) + ) + console.log('displaying chip', item, val, extraArgs) + return chipBuilder(val, item, valuesets, extraArgs) + }) + .filter((label) => !!label) + return chips +} diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/renderers.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/renderers.tsx new file mode 100644 index 000000000..39797f1dc --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/renderers.tsx @@ -0,0 +1,287 @@ +import React from 'react' +import { CriteriaFormItemView, CriteriaFormItemType } from '../types' +import { renderLabel } from '../components' +import CalendarRange from 'components/ui/Inputs/CalendarRange' +import { + Alert, + Autocomplete, + Checkbox, + FormControlLabel, + FormLabel, + Grid, + Radio, + RadioGroup, + Switch, + TextField, + Typography +} from '@mui/material' +import ExecutiveUnitsInput from 'components/ui/Inputs/ExecutiveUnit' +import OccurenceInput from 'components/ui/Inputs/Occurences' +import SearchbarWithCheck from 'components/ui/Inputs/SearchbarWithCheck' +import AsyncAutocomplete from 'components/ui/Inputs/AsyncAutocomplete' +import CheckedTextfield from 'components/ui/Inputs/CheckedTextfield' +import { isArray } from 'lodash' +import useStyles from '../style' +import { useAppSelector } from 'state' +import { BlockWrapper } from 'components/ui/Layout' +import { fetchValueSet } from 'services/aphp/callApi' +import DurationRange from 'components/ui/Inputs/DurationRange' +import { IndeterminateCheckBoxOutlined } from '@mui/icons-material' +import _ from 'lodash' + +/************************************************************************************/ +/* Criteria Form Item Renderer */ +/************************************************************************************/ +/* +This file contains the list of functions used to render the React view form for each CriteriaFormItemType. +*/ + +const FORM_ITEM_RENDERER: { [key in CriteriaFormItemType]: CriteriaFormItemView } = { + info: (props) => { + return {props.definition.content} + }, + text: (props) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { classes } = useStyles() + return ( + props.updateData(e.target.value)} + /> + ) + }, + textWithRegex: (props) => { + return ( + props.setError(isError ? 'error' : undefined)} + onChange={(value) => props.updateData(value)} + /> + ) + }, + durationRange: (props) => { + return ( + + !range[0] && !range[1] + ? props.updateData(null) + : props.updateData({ start: range[0] ?? null, end: range[1] ?? null, includeNull: false }) + } + onError={(isError) => props.setError(isError ? 'error' : undefined)} + deidentified={props.deidentified} + /> + ) + }, + calendarRange: (props) => ( + + !range[0] && !range[1] + ? props.updateData(null) + : props.updateData({ start: range[0] ?? null, end: range[1] ?? null, includeNull }) + } + onError={(isError) => props.setError(isError ? props.definition.errorType : undefined)} + includeNullValues={props.value?.includeNull} + onChangeIncludeNullValues={ + props.definition.withOptionIncludeNull + ? () => { + /* dummy TODO change CalendarRange to accept a boolean to activate the includeNull checkbox */ + } + : undefined + } + /> + ), + autocomplete: (props) => { + const codeSystem = props.getValueSetOptions(props.definition.valueSetId) + const groupBy = props.definition.groupBy + const valueWithLabels = (props.value ?? []).map((code) => codeSystem.find((c) => c.id === code.id) ?? code) + const value = props.definition.singleChoice ? valueWithLabels?.at(0) ?? null : valueWithLabels ?? [] + return ( + option.label} + isOptionEqualToValue={(option, value) => option.id === value.id} + value={value} + onChange={(e, value) => props.updateData(value ? (isArray(value) ? value : [value]) : null)} + renderInput={(params) => } + groupBy={groupBy ? (option) => option[groupBy] ?? '' : undefined} + renderGroup={ + groupBy + ? (params) => { + const { group, children } = params + const groupChildren = codeSystem.filter((doc) => doc[groupBy] === group) + const selectedWithinGroup = valueWithLabels.filter((doc) => doc[groupBy] === group) + + const onClick = () => { + if (groupChildren.length === selectedWithinGroup.length) { + props.updateData(valueWithLabels.filter((doc) => doc[groupBy] !== group)) + } else { + props.updateData(_.uniqWith([...valueWithLabels, ...groupChildren], _.isEqual)) + } + } + + return ( + + + 0 + } + checked={groupChildren.length === selectedWithinGroup.length} + onClick={onClick} + indeterminateIcon={} + /> + + {group} + + + {children} + + ) + } + : undefined + } + /> + ) + }, + radioChoice: (props) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { classes } = useStyles() + return ( + props.updateData(value)} + > + {props.definition.choices.map((choice, index) => ( + } label={choice.label} /> + ))} + + ) + }, + executiveUnit: (props) => ( + props.updateData(value)} + /> + ), + number: (props) => { + // TODO add regex number check ? and set type text + return ( + props.updateData(e.target.value ? parseFloat(e.target.value) : null)} + placeholder={props.definition.label} + disabled={props.disabled} + /> + ) + }, + numberAndComparator: (props) => { + const nonNullValue = props.value ?? { value: 0, comparator: 'GREATER_OR_EQUAL' } + return ( + { + props.updateData({ value: newCount, comparator: newComparator, maxValue: maxValue }) + }} + withHierarchyInfo={props.definition.withHierarchyInfo} + disabled={props.disabled} + /> + ) + }, + boolean: (props) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { classes } = useStyles() + return ( + + {props.definition.label} + props.updateData(event.target.checked)} color="secondary" /> + + ) + }, + textWithCheck: (props) => ( + props.updateData(value)} + placeholder={props.definition.placeholder} + onError={(isError) => props.setError(isError ? props.definition.errorType : undefined)} + /> + ), + codeSearch: (props) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const codeCaches = useAppSelector((state) => state.valueSets.cache) + const valueWithLabels = (props.value ?? []).map( + (code) => + (code.system && codeCaches[code.system]?.find((c) => c.id === code.id)) || + Object.keys(codeCaches) + .flatMap((key) => codeCaches[key]) + .find((c) => c.id === code.id) || + code + ) + return ( + props.searchCode(search, props.definition.valueSetIds.join(','), signal)} + onChange={(value) => { + // TODO this is a temporary fix that should be properly addressed with the new code search component + if (props.definition.checkIsLeaf) { + ;(async () => { + const valuesWithLeafInfo = await Promise.all( + value.map(async (v) => { + const res = await fetchValueSet(v.system ?? props.definition.valueSetIds.join(','), { + valueSetTitle: 'Toute la hiérarchie', + code: v.id, + noStar: true + }) + return { ...v, isLeaf: res?.length === 0 } + }) + ) + props.updateData(valuesWithLeafInfo) + })() + } else { + props.updateData(value) + } + }} + /> + ) + } +} + +export default FORM_ITEM_RENDERER diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Form/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/style.ts similarity index 100% rename from src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Form/styles.ts rename to src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/style.ts diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types.ts new file mode 100644 index 000000000..f020ea448 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types.ts @@ -0,0 +1,344 @@ +import { ReactNode } from 'react' +import { ScopeElement } from 'types' +import { Hierarchy } from 'types/hierarchy' +import { Comparators, CriteriaType } from 'types/requestCriterias' +import { SourceType } from 'types/scope' +import { LabelObject } from 'types/searchCriterias' +import { CHIPS_DISPLAY_METHODS } from './mappers/chipDisplayMapper' +import { BUILD_MAPPERS, UNBUILD_MAPPERS } from './mappers/buildMappers' + +/********************************************************************************/ +/* Criteria Types */ +/********************************************************************************/ +// When adding a new criteria type you must complete the following steps: +// 1. **REQUIRED** create a new CriteriaItem type (with required props "type") and then add the new type to the CriteriaItems union type +// 2. Optionnaly create the new data type definition and add it to the DataType union type +// 3. **REQUIRED** Update the DataTypeMapping type with the new type mapping + +/****************************************************************/ +/* Criteria Item Definition Types */ +/****************************************************************/ + +export type Context = { + deidentified: boolean +} + +type BaseCriteriaItem = { + label?: string + labelAltStyle?: boolean + info?: string + // these are used to display external label and info on top of the component + extraLabel?: string | ((data: Record, context: Context) => string) + extraInfo?: string + // for conditionnal fields + displayCondition?: ((data: Record, context: Context) => boolean) | string + disableCondition?: ((data: Record, context: Context) => boolean) | string +} + +type WithLabel = { + label: string +} + +type WithErrorType = { + errorType: string +} + +export type TextCriteriaItem = BaseCriteriaItem & { + type: 'text' +} + +export type TextWithRegexCriteriaItem = BaseCriteriaItem & { + type: 'textWithRegex' + regex: string + checkErrorMessage?: string + placeholder?: string + multiline?: boolean + inverseCheck?: boolean + extractValidValues?: boolean +} + +export type InfoCriteriaItem = BaseCriteriaItem & { + type: 'info' + content: string + contentType: 'info' | 'warning' | 'error' +} + +export type NumberCriteriaItem = BaseCriteriaItem & { + type: 'number' +} + +export type BooleanCriteriaItem = BaseCriteriaItem & { + type: 'boolean' +} + +export type RadioChoiceCriteriaItem = BaseCriteriaItem & { + type: 'radioChoice' + choices: LabelObject[] +} + +export type NumberWithComparatorCriteriaItem = BaseCriteriaItem & + WithLabel & { + type: 'numberAndComparator' + withHierarchyInfo?: boolean + floatValues?: boolean + allowBetween?: boolean + } + +export type DurationItem = BaseCriteriaItem & { + type: 'durationRange' +} + +export type CalendarItem = BaseCriteriaItem & + WithErrorType & { + type: 'calendarRange' + withOptionIncludeNull?: boolean + } + +export type AutoCompleteItem = BaseCriteriaItem & { + type: 'autocomplete' + noOptionsText: string + singleChoice?: boolean + valueSetId: string + valueSetData?: LabelObject[] + groupBy?: 'system' | 'type' +} + +/** + * Code search criteria item + */ +export type CodeSearchItem = BaseCriteriaItem & { + type: 'codeSearch' + noOptionsText: string + checkIsLeaf?: boolean + /** Ids (urls) of valuesets that are allowed to be used for this code search */ + valueSetIds: string[] +} + +export type ExecutiveUnitItem = BaseCriteriaItem & + WithLabel & { + type: 'executiveUnit' + sourceType: SourceType + } + +export type TextWithCheckItem = BaseCriteriaItem & + WithErrorType & { + type: 'textWithCheck' + placeholder: string + } + +// Union of all criteria item types +export type CriteriaItems = + | CalendarItem + | AutoCompleteItem + | ExecutiveUnitItem + | TextWithCheckItem + | BooleanCriteriaItem + | TextCriteriaItem + | NumberCriteriaItem + | CodeSearchItem + | NumberWithComparatorCriteriaItem + | TextWithRegexCriteriaItem + | RadioChoiceCriteriaItem + | DurationItem + | InfoCriteriaItem + +/****************************************************************/ +/* Criteria Data Types */ +/****************************************************************/ + +export type NewDurationRangeType = { + start: string | null + end: string | null + includeNull?: boolean +} + +export type NumberAndComparatorDataType = { + value: number + comparator: Comparators + maxValue?: number +} + +// Union of all criteria data types +export type DataTypes = + | NewDurationRangeType + | string + | LabelObject[] + | number + | Hierarchy[] + | NumberAndComparatorDataType + | boolean + | null + +/****************************************************************/ +/* Type Mapping / Criteria Item type */ +/****************************************************************/ + +type CriteriaTypeMapping = { + definition: T + dataType: U +} + +// Mapping of criteria item types to their respective data types and definitions +export type DataTypeMapping = { + calendarRange: CriteriaTypeMapping + durationRange: CriteriaTypeMapping + text: CriteriaTypeMapping + autocomplete: CriteriaTypeMapping + number: CriteriaTypeMapping + executiveUnit: CriteriaTypeMapping[]> + numberAndComparator: CriteriaTypeMapping + boolean: CriteriaTypeMapping + textWithCheck: CriteriaTypeMapping + codeSearch: CriteriaTypeMapping + textWithRegex: CriteriaTypeMapping + radioChoice: CriteriaTypeMapping + info: CriteriaTypeMapping +} + +// List of criteria item types +export type CriteriaFormItemType = keyof DataTypeMapping + +export type DataTypeMappings = DataTypeMapping[CriteriaFormItemType] + +/********************************************************************************/ +/* Criteria Form Types */ +/********************************************************************************/ + +/****************************************************************/ +/* Criteria Form Data */ +/****************************************************************/ + +/** + * Common criteria data for criteria form + */ +export type CommonCriteriaData = { + /** Criteria id */ + id: number + /** Type of the criteria */ + type: CriteriaType + title: string + isInclusive: boolean + encounterService: Hierarchy[] | null + error?: boolean +} + +// helpers +export type WithOccurenceCriteriaDataType = { + occurrence: NumberAndComparatorDataType + startOccurrence: NewDurationRangeType | null +} + +export type WithEncounterDateDataType = { + encounterStartDate: NewDurationRangeType | null + encounterEndDate: NewDurationRangeType | null +} + +export type WithEncounterStatusDataType = { + encounterStatus: LabelObject[] +} + +/****************************************************************/ +/* Criteria Form Definition */ +/****************************************************************/ + +export type BuildMethodExtraParam = + | { + type: 'string' + value: string + } + | { + type: 'number' + value: number + } + | { + type: 'boolean' + value: boolean + } + | { + type: 'null' + value: null + } + | { + type: 'method' + value: keyof typeof BUILD_MAPPERS | keyof typeof UNBUILD_MAPPERS | keyof typeof CHIPS_DISPLAY_METHODS + } + | { + type: 'reference' + value: string + } + +export type FhirKey = + | string + | { id: string; type: string } + | { main: string; deid: string } + | { main: string; alt: string; value1: BuildMethodExtraParam; value2: BuildMethodExtraParam } + +export type CriteriaItemBuildInfo = { + buildInfo?: { + fhirKey?: FhirKey + buildMethod?: keyof typeof BUILD_MAPPERS + buildMethodExtraArgs?: Array + unbuildMethod?: keyof typeof UNBUILD_MAPPERS + chipDisplayMethod?: keyof typeof CHIPS_DISPLAY_METHODS + chipDisplayMethodExtraArgs?: Array + } +} + +export type GenericCriteriaItem = CriteriaItems & CriteriaItemBuildInfo + +export type WithBuildInfo = T extends CriteriaTypeMapping< + infer DefinitionType, + // Used to be useful for the buildInfo type but not anymore, though didn't find a typing replacement to extract the DefintionType without The DataType + // eslint-disable-next-line @typescript-eslint/no-unused-vars + infer DataType +> + ? DefinitionType & CriteriaItemBuildInfo + : never + +/** + * Criteria item definition + */ +export type CriteriaItem = { + /** key of the related value in the data object */ + valueKey?: keyof T +} & WithBuildInfo + +// Criteria section listing criteria items +export type CriteriaSection = { + title?: string + defaulCollapsed?: boolean + items: CriteriaItem[] +} + +// Criteria form definition +export type CriteriaForm = { + label: string + warningAlert?: ReactNode[] + initialData: Omit + errorMessages: { [key: string]: string } + buildInfo?: { + defaultFilter?: string + criteriaType: CriteriaType | CriteriaType[] + resourceType: string | string[] + // should return true if the criteria is to be unbuild for the current fhir filter + subType?: string + } + itemSections: CriteriaSection[] +} + +/****************************************************************/ +/* Criteria Item Render */ +/****************************************************************/ + +export type CriteriaFormItemViewProps = { + value: DataTypeMapping[T]['dataType'] + definition: DataTypeMapping[T]['definition'] + disabled: boolean + getValueSetOptions: (valueSetId: string) => LabelObject[] + searchCode: (code: string, codeSystemUrl: string, abortSignal: AbortSignal) => Promise + updateData: (value: DataTypeMapping[T]['dataType'] | null) => void + setError: (error?: string) => void + deidentified: boolean +} + +export type CriteriaFormItemView = React.FC> diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaRightPanel.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaRightPanel.tsx index 6e07290ae..bbb4b1977 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaRightPanel.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaRightPanel.tsx @@ -23,12 +23,14 @@ import CribIcon from '@mui/icons-material/Crib' import PregnantWomanIcon from '@mui/icons-material/PregnantWoman' import DomainAddIcon from '@mui/icons-material/DomainAdd' -import { CriteriaItemDataCache, CriteriaItemType } from 'types' +import { CriteriaItemType } from 'types' import useStyles from './styles' import { CriteriaType, SelectedCriteriaType } from 'types/requestCriterias' import { PhotoCameraFront } from '@mui/icons-material' import { CriteriaState } from 'state/criteria' import criteriaList from 'components/CreationCohort/DataList_Criteria' +import CriteriaForm from './CriteriaForm' +import { fetchValueSet } from 'services/aphp/callApi' type CriteriaTypesWithIcons = Exclude< CriteriaType, @@ -45,7 +47,7 @@ type CriteriaListItemProps = { const CriteriaListItem: React.FC = (props) => { const { criteriaItem, handleClick } = props - const { color, title, components, subItems, disabled, id, fontWeight } = criteriaItem + const { color, title, formDefinition, subItems, disabled, id, fontWeight } = criteriaItem const { classes } = useStyles() const [open, setOpen] = useState(true) @@ -78,7 +80,7 @@ const CriteriaListItem: React.FC = (props) => { const svgIcon = getCriteriaIcon(id as CriteriaTypesWithIcons) - const pointer = components ? 'pointer' : 'default' + const pointer = formDefinition ? 'pointer' : 'default' const cursor = disabled ? 'not-allowed' : pointer @@ -168,8 +170,6 @@ const CriteriaRightPanel: React.FC = (props) => { const { classes } = useStyles() const [action, setAction] = useState(null) - const DrawerComponent = action ? action.components : null - const _onChangeSelectedCriteria = (newSelectedCriteria: SelectedCriteriaType) => { const _newSelectedCriteria = { ...newSelectedCriteria, @@ -213,21 +213,39 @@ const CriteriaRightPanel: React.FC = (props) => { } }, [open]) // eslint-disable-line + const renderCriteriaForm = (criteriaItem: CriteriaItemType) => { + if (criteriaItem.component) { + return React.createElement(criteriaItem.component, { + parentId, + selectedCriteria, + onChangeSelectedCriteria: _onChangeSelectedCriteria, + goBack: () => setAction(null) + }) + } else if (criteriaItem.formDefinition) { + return ( + setAction(null)} + data={selectedCriteria ?? undefined} + searchCode={(code: string, codeSystemUrl: string, abortSignal: AbortSignal) => + fetchValueSet( + codeSystemUrl, + { valueSetTitle: 'Toute la hiérarchie', search: code, noStar: false }, + abortSignal + ) + } + /> + ) + } + return null + } + return (
- {/* DrawerComponent = action.components */} - {DrawerComponent && action ? ( - c.criteriaType === action.id) || - ({ criteriaType: action.id, data: {} } as CriteriaItemDataCache) - } - selectedCriteria={selectedCriteria} - onChangeSelectedCriteria={_onChangeSelectedCriteria} - goBack={() => setAction(null)} - /> + {action ? ( + renderCriteriaForm(action) ) : ( <> diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DemographicForm/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DemographicForm/index.tsx deleted file mode 100644 index 2b42ac562..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DemographicForm/index.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import React, { useState } from 'react' - -import { - Alert, - Autocomplete, - Button, - Divider, - FormLabel, - Grid, - IconButton, - Typography, - TextField, - Switch -} from '@mui/material' - -import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace' - -import useStyles from './styles' - -import { useAppSelector } from 'state' - -import { DurationRangeType, LabelObject, VitalStatusLabel, VitalStatusOptionsLabel } from 'types/searchCriterias' -import CalendarRange from 'components/ui/Inputs/CalendarRange' -import DurationRange from 'components/ui/Inputs/DurationRange' -import { CriteriaDataKey, DemographicDataType, CriteriaType } from 'types/requestCriterias' -import { BlockWrapper } from 'components/ui/Layout' -import { CriteriaDrawerComponentProps, CriteriaItemDataCache } from 'types' -import { CriteriaLabel } from 'components/ui/CriteriaLabel' - -enum Error { - INCOHERENT_AGE_ERROR, - NO_ERROR -} - -export const mappingCriteria = ( - criteriaToMap: LabelObject[] | null, - key: CriteriaDataKey, - mapping: CriteriaItemDataCache -) => { - if (criteriaToMap) { - return criteriaToMap.map((criteria) => { - const mappedCriteria = mapping.data?.[key]?.find((c: LabelObject) => c?.id === criteria?.id) - return mappedCriteria - }) - } else { - return [] - } -} - -const DemographicForm = (props: CriteriaDrawerComponentProps) => { - const { criteriaData, onChangeSelectedCriteria, goBack } = props - const selectedCriteria: DemographicDataType | null = props.selectedCriteria as DemographicDataType - const [birthdates, setBirthdates] = useState(selectedCriteria?.birthdates || [null, null]) - const [deathDates, setDeathDates] = useState(selectedCriteria?.deathDates || [null, null]) - const [age, setAge] = useState(selectedCriteria?.age || [null, null]) - const [vitalStatus, setVitalStatus] = - useState(mappingCriteria(selectedCriteria?.vitalStatus, CriteriaDataKey.VITALSTATUS, criteriaData)) || [] - const [genders, setGenders] = - useState(mappingCriteria(selectedCriteria?.genders, CriteriaDataKey.GENDER, criteriaData)) || [] - const [title, setTitle] = useState(selectedCriteria?.title || 'Critère démographique') - const [isInclusive, setIsInclusive] = useState( - selectedCriteria?.isInclusive === undefined ? true : selectedCriteria?.isInclusive - ) - - const selectedPopulation = useAppSelector((state) => state.cohortCreation.request.selectedPopulation || []) - - const deidentified: boolean | undefined = - selectedPopulation === null - ? undefined - : selectedPopulation - .map((population) => population && population.access) - .filter((elem) => elem && elem === 'Pseudonymisé').length > 0 - - const { classes } = useStyles() - - const [error, setError] = useState(Error.NO_ERROR) - const [multiFields, setMultiFields] = useState(localStorage.getItem('multiple_fields')) - const isEdition = selectedCriteria !== null || false - - const onSubmit = () => { - onChangeSelectedCriteria({ - id: selectedCriteria?.id, - age, - birthdates, - deathDates, - genders, - isInclusive, - vitalStatus, - title, - type: CriteriaType.PATIENT - }) - } - return ( - - - {!isEdition ? ( - <> - - - - - Ajouter un critère démographique - - ) : ( - Modifier un critère démographique - )} - - - - {!multiFields && ( - { - localStorage.setItem('multiple_fields', 'ok') - setMultiFields('ok') - }} - > - Tous les éléments des champs multiples sont liés par une contrainte OU - - )} - - - Démographie patient - - setTitle(e.target.value)} - /> - - - - Exclure les patients qui suivent les règles suivantes - - setIsInclusive(!event.target.checked)} - color="secondary" - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={genders} - onChange={(e, value) => setGenders(value)} - renderInput={(params) => } - /> - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={vitalStatus} - onChange={(e, value) => { - setVitalStatus(value) - if (value.length === 1 && value[0].label === VitalStatusLabel.ALIVE) setDeathDates([null, null]) - }} - renderInput={(params) => } - /> - - {!deidentified && ( - - - setBirthdates(value)} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - /> - - )} - - - status.label === VitalStatusLabel.DECEASED) - ? VitalStatusOptionsLabel.deceasedAge - : VitalStatusOptionsLabel.age - } - /> - setAge(value)} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - deidentified={deidentified} - /> - - {!deidentified && - vitalStatus && - (vitalStatus.length === 0 || - (vitalStatus.length === 1 && - vitalStatus.find((status: LabelObject) => status.label === VitalStatusLabel.DECEASED))) && ( - - - setDeathDates(value)} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - /> - - )} - - - - {!isEdition && ( - - )} - - - - - ) -} - -export default DemographicForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DemographicForm/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DemographicForm/styles.ts deleted file mode 100644 index 9e7a8bf6b..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DemographicForm/styles.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - root: { - display: 'flex', - flexDirection: 'column', - flex: '1 1 auto' - }, - actionContainer: { - display: 'flex', - alignItems: 'center', - height: 72, - padding: 20, - backgroundColor: '#317EAA', - color: 'white' - }, - backButton: { color: 'white' }, - divider: { background: 'white' }, - titleLabel: { marginLeft: '1em' }, - formContainer: { - overflow: 'auto', - maxHeight: 'calc(100vh - 135px)' - }, - inputContainer: { - padding: '1em', - display: 'flex', - flex: '1 1 0%', - flexDirection: 'column' - }, - inputItem: { - margin: '1em', - width: 'calc(100% - 2em)' - }, - criteriaActionContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexWrap: 'wrap', - borderTop: '1px solid grey', - position: 'absolute', - width: '100%', - bottom: 0, - left: 0, - background: '#fff', - '& > button': { - margin: '12px 8px' - } - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DocumentsForm/DocumentsForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DocumentsForm/DocumentsForm.tsx deleted file mode 100644 index c43918b47..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DocumentsForm/DocumentsForm.tsx +++ /dev/null @@ -1,328 +0,0 @@ -import React, { useEffect, useState } from 'react' -import _ from 'lodash' - -import { - Alert, - Autocomplete, - Button, - Checkbox, - CircularProgress, - Divider, - FormControl, - FormLabel, - Grid, - IconButton, - InputLabel, - MenuItem, - Select, - Switch, - TextField, - Typography -} from '@mui/material' - -import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace' - -import AdvancedInputs from '../AdvancedInputs/AdvancedInputs' - -import useStyles from './styles' - -import { CriteriaDrawerComponentProps, SimpleCodeType } from 'types' -import services from 'services/aphp' -import { useDebounce } from 'utils/debounce' -import { SearchByTypes, FilterByDocumentStatus } from 'types/searchCriterias' -import { IndeterminateCheckBoxOutlined } from '@mui/icons-material' -import { SearchInputError } from 'types/error' -import { Comparators, DocumentDataType, CriteriaType } from 'types/requestCriterias' -import Searchbar from 'components/ui/Searchbar' -import SearchInput from 'components/ui/Searchbar/SearchInput' -import { BlockWrapper } from 'components/ui/Layout' -import OccurenceInput from 'components/ui/Inputs/Occurences' -import { SourceType } from 'types/scope' - -const defaultComposition: Omit = { - type: CriteriaType.DOCUMENTS, - title: 'Critère de document', - search: '', - searchBy: SearchByTypes.TEXT, - docType: [], - occurrence: 1, - occurrenceComparator: Comparators.GREATER_OR_EQUAL, - encounterStartDate: [null, null], - encounterEndDate: [null, null], - includeEncounterStartDateNull: false, - includeEncounterEndDateNull: false, - startOccurrence: [null, null], - isInclusive: true, - docStatuses: [], - encounterStatus: [] -} - -enum Error { - ADVANCED_INPUTS_ERROR, - NO_ERROR -} - -const DocumentsForm: React.FC = (props) => { - const { criteriaData, selectedCriteria, onChangeSelectedCriteria, goBack } = props - - const { classes } = useStyles() - const [defaultValues, setDefaultValues] = useState( - (selectedCriteria as DocumentDataType) || defaultComposition - ) - const [multiFields, setMultiFields] = useState(localStorage.getItem('multiple_fields')) - const [searchCheckingLoading, setSearchCheckingLoading] = useState(false) - const [searchInputError, setSearchInputError] = useState(undefined) - const [occurrence, setOccurrence] = useState(defaultValues.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - defaultValues.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const debouncedSearchItem = useDebounce(500, defaultValues.search) - - const isEdition = selectedCriteria !== null ? true : false - - const docStatuses: string[] = [FilterByDocumentStatus.VALIDATED, FilterByDocumentStatus.NOT_VALIDATED] - - const [error, setError] = useState(Error.NO_ERROR) - - const _onSubmit = () => { - onChangeSelectedCriteria({ ...defaultValues, occurrence: occurrence, occurrenceComparator: occurrenceComparator }) - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const _onChangeValue = (key: string, value: any) => { - setDefaultValues((prevValues) => { - const _defaultValues = { ...prevValues, [key]: value } - return _defaultValues - }) - } - - useEffect(() => { - const checkDocumentSearch = async () => { - try { - setSearchCheckingLoading(true) - setSearchInputError({ ...searchInputError, isError: true }) - const checkDocumentSearch = await services.cohorts.checkDocumentSearchInput(defaultValues.search) - - setSearchInputError(checkDocumentSearch) - setSearchCheckingLoading(false) - } catch (error) { - console.error('Erreur lors de la vérification du champ de recherche', error) - setSearchCheckingLoading(false) - } - } - - checkDocumentSearch() - }, [debouncedSearchItem]) - - return ( - - - {!isEdition ? ( - <> - - - - - Ajouter un critère de documents médicaux - - ) : ( - Modifier un critère de documents médicaux - )} - - - - {!multiFields && ( - { - localStorage.setItem('multiple_fields', 'ok') - setMultiFields('ok') - }} - > - Tous les éléments des champs multiples sont liés par une contrainte OU - - )} - - - Documents médicaux - - _onChangeValue('title', e.target.value)} - /> - - - _onChangeValue('isInclusive', !defaultValues.isInclusive)} - style={{ margin: 'auto 1em' }} - component="legend" - > - Exclure les patients qui suivent les règles suivantes - - _onChangeValue('isInclusive', !event.target.checked)} - color="secondary" - /> - - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - /> - - - - Rechercher dans : - - - - - - - _onChangeValue('search', newValue)} - /> - - - {searchCheckingLoading && ( - - - Vérification du champ de recherche en cours... - - )} - - - option.label} - isOptionEqualToValue={(option, value) => _.isEqual(option, value)} - value={defaultValues.docType || undefined} - onChange={(e, value) => _onChangeValue('docType', value)} - renderInput={(params) => } - groupBy={(doctype) => doctype.type} - disableCloseOnSelect - renderGroup={(docType) => { - const currentDocTypeList = criteriaData.data.docTypes - ? criteriaData.data.docTypes.filter((doc: SimpleCodeType) => doc.type === docType.group) - : [] - - const currentSelectedDocTypeList = defaultValues.docType - ? defaultValues.docType.filter((doc) => doc.type === docType.group) - : [] - - const onClick = () => { - if (currentDocTypeList.length === currentSelectedDocTypeList.length) { - _onChangeValue( - 'docType', - defaultValues.docType?.filter((doc) => doc.type !== docType.group) - ) - } else { - _onChangeValue( - 'docType', - _.uniqWith([...(defaultValues.docType || []), ...currentDocTypeList], _.isEqual) - ) - } - } - - return ( - - - 0 - } - checked={currentDocTypeList.length === currentSelectedDocTypeList.length} - onClick={onClick} - indeterminateIcon={} - /> - - {docType.group} - - - {docType.children} - - ) - }} - /> - - - _onChangeValue('docStatuses', value)} - options={docStatuses} - value={defaultValues.docStatuses || undefined} - renderInput={(params) => } - /> - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={defaultValues.encounterStatus} - onChange={(e, value) => _onChangeValue('encounterStatus', value)} - renderInput={(params) => } - /> - - - setError(isError ? Error.ADVANCED_INPUTS_ERROR : Error.NO_ERROR)} - /> - - - - {!isEdition && ( - - )} - - - - - ) -} - -export default DocumentsForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DocumentsForm/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DocumentsForm/styles.ts deleted file mode 100644 index 0438c36ca..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DocumentsForm/styles.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - root: { - display: 'flex', - flexDirection: 'column', - flex: '1 1 auto' - }, - actionContainer: { - display: 'flex', - alignItems: 'center', - height: 72, - padding: 20, - backgroundColor: '#317EAA', - color: 'white' - }, - backButton: { color: 'white' }, - divider: { background: 'white' }, - titleLabel: { marginLeft: '1em' }, - formContainer: { - overflow: 'auto', - maxHeight: 'calc(100vh - 135px)' - }, - inputContainer: { - padding: '1em', - display: 'flex', - flex: '1 1 0%', - flexDirection: 'column' - }, - inputItem: { - margin: '1em', - width: 'calc(100% - 2em)' - }, - criteriaActionContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexWrap: 'wrap', - borderTop: '1px solid grey', - position: 'absolute', - width: '100%', - bottom: 0, - left: 0, - background: '#fff', - '& > button': { - margin: '12px 8px' - } - }, - searchInput: { - margin: '0px !important', - '& > div': { - borderRadius: 5, - height: 50, - padding: '10px 0px' - } - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/EncounterForm/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/EncounterForm/index.tsx deleted file mode 100644 index b33c1dce8..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/EncounterForm/index.tsx +++ /dev/null @@ -1,434 +0,0 @@ -import React, { useEffect, useState } from 'react' - -import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace' -import { - Alert, - Autocomplete, - Button, - Divider, - FormLabel, - Grid, - IconButton, - Switch, - TextField, - Typography -} from '@mui/material' - -import useStyles from './styles' -import { useAppSelector } from 'state' - -import { infoMessages } from 'data/infoMessage' - -import { CriteriaDrawerComponentProps, ScopeElement } from 'types' -import { DurationRangeType, LabelObject } from 'types/searchCriterias' -import { Comparators, CriteriaDataKey, EncounterDataType, CriteriaType, ResourceType } from 'types/requestCriterias' -import { BlockWrapper } from 'components/ui/Layout' -import OccurenceInput from 'components/ui/Inputs/Occurences' -import Collapse from 'components/ui/Collapse' -import CalendarRange from 'components/ui/Inputs/CalendarRange' -import DurationRange from 'components/ui/Inputs/DurationRange' -import { mappingCriteria } from '../DemographicForm' -import { SourceType } from 'types/scope' -import { Hierarchy } from 'types/hierarchy' -import { CriteriaLabel } from 'components/ui/CriteriaLabel' -import ExecutiveUnitsInput from 'components/ui/Inputs/ExecutiveUnit' - -enum Error { - EMPTY_FORM, - EMPTY_DURATION_ERROR, - EMPTY_AGE_ERROR, - MIN_MAX_AGE_ERROR, - MIN_MAX_DURATION_ERROR, - INCOHERENT_DURATION_ERROR, - INCOHERENT_AGE_ERROR, - INCOHERENT_DURATION_ERROR_AND_INCOHERENT_AGE_ERROR, - NO_ERROR -} - -const EncounterForm = ({ - criteriaData, - selectedCriteria, - goBack, - onChangeSelectedCriteria -}: CriteriaDrawerComponentProps) => { - const criteria = selectedCriteria as EncounterDataType - const [title, setTitle] = useState(criteria?.title || 'Critère de prise en charge') - const [age, setAge] = useState(criteria?.age || [null, null]) - const [duration, setDuration] = useState(criteria?.duration || [null, null]) - const [admissionMode, setAdmissionMode] = useState( - mappingCriteria(criteria?.admissionMode, CriteriaDataKey.ADMISSION_MODE, criteriaData) || [] - ) - const [entryMode, setEntryMode] = useState( - mappingCriteria(criteria?.entryMode, CriteriaDataKey.ENTRY_MODES, criteriaData) || [] - ) - const [exitMode, setExitMode] = useState( - mappingCriteria(criteria?.exitMode, CriteriaDataKey.EXIT_MODES, criteriaData) || [] - ) - const [priseEnChargeType, setPriseEnChargeType] = useState( - mappingCriteria(criteria?.priseEnChargeType, CriteriaDataKey.PRISE_EN_CHARGE_TYPE, criteriaData) || [] - ) - const [typeDeSejour, setTypeDeSejour] = useState( - mappingCriteria(criteria?.typeDeSejour, CriteriaDataKey.TYPE_DE_SEJOUR, criteriaData) || [] - ) - const [reason, setReason] = useState( - mappingCriteria(criteria?.reason, CriteriaDataKey.REASON, criteriaData) || [] - ) - const [destination, setDestination] = useState( - mappingCriteria(criteria?.destination, CriteriaDataKey.DESTINATION, criteriaData) || [] - ) - const [provenance, setProvenance] = useState( - mappingCriteria(criteria?.provenance, CriteriaDataKey.PROVENANCE, criteriaData) || [] - ) - const [admission, setAdmission] = useState( - mappingCriteria(criteria?.admission, CriteriaDataKey.ADMISSION, criteriaData) || [] - ) - const [encounterService, setEncounterService] = useState[]>( - criteria?.encounterService || [] - ) - const [encounterStartDate, setEncounterStartDate] = useState( - criteria?.encounterStartDate || [null, null] - ) - const [includeEncounterStartDateNull, setIncludeEncounterStartDateNull] = useState( - criteria?.includeEncounterStartDateNull - ) - const [encounterEndDate, setEncounterEndDate] = useState( - criteria?.encounterEndDate || [null, null] - ) - const [includeEncounterEndDateNull, setIncludeEncounterEndDateNull] = useState(criteria?.includeEncounterEndDateNull) - const [occurrence, setOccurrence] = useState(criteria?.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - criteria?.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [isInclusive, setIsInclusive] = useState( - selectedCriteria?.isInclusive === undefined ? true : selectedCriteria?.isInclusive - ) - const [encounterStatus, setEncounterStatus] = useState( - mappingCriteria(criteria?.encounterStatus, CriteriaDataKey.ENCOUNTER_STATUS, criteriaData) || [] - ) - - const { classes } = useStyles() - const [multiFields, setMultiFields] = useState(localStorage.getItem('multiple_fields')) - const isEdition = selectedCriteria !== null || false - const [error, setError] = useState(Error.NO_ERROR) - const selectedPopulation = useAppSelector((state) => state.cohortCreation.request.selectedPopulation || []) - - const deidentified: boolean | undefined = - selectedPopulation === null - ? undefined - : selectedPopulation - .map((population) => population && population.access) - .filter((elem) => elem && elem === 'Pseudonymisé').length > 0 - - useEffect(() => { - setError(Error.NO_ERROR) - if ( - (occurrence === 0 && occurrenceComparator === Comparators.EQUAL) || - (occurrence === 1 && occurrenceComparator === Comparators.LESS) || - (occurrence === 0 && occurrenceComparator === Comparators.LESS_OR_EQUAL) - ) { - setError(Error.EMPTY_FORM) - } - }, [occurrence, occurrenceComparator]) - - const onSubmit = () => { - onChangeSelectedCriteria({ - id: criteria?.id, - age, - duration, - admissionMode, - entryMode, - exitMode, - priseEnChargeType, - typeDeSejour, - reason, - destination, - provenance, - admission, - encounterService, - encounterStartDate, - includeEncounterStartDateNull, - encounterEndDate, - includeEncounterEndDateNull, - occurrence, - occurrenceComparator, - encounterStatus, - startOccurrence: [null, null], - isInclusive, - title, - type: CriteriaType.ENCOUNTER - }) - } - return ( - - - {!isEdition ? ( - <> - - - - - Ajouter un critère prise en charge - - ) : ( - Modifier un critère prise en charge - )} - - - - {error === Error.NO_ERROR && !multiFields && ( - Tous les éléments des champs multiples sont liés par une contrainte OU - )} - {infoMessages.map( - (infoMessage) => - infoMessage.resourceType === ResourceType.ENCOUNTER && ( - - {infoMessage.message} - - ) - )} - {error === Error.EMPTY_FORM && ( - Merci de renseigner au moins un nombre d'occurence supérieur ou égal à 1 - )} - - - Prise en charge - - - - setTitle(e.target.value)} - /> - - - - Exclure les patients qui suivent les règles suivantes - setIsInclusive(!event.target.checked)} - color="secondary" - /> - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - /> - - - - - - - - - - setAge(value)} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - deidentified={deidentified} - /> - - - - - setDuration(value)} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - /> - - - - - - - Début de prise en charge - - setEncounterStartDate(newStartDate)} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - includeNullValues={includeEncounterStartDateNull} - onChangeIncludeNullValues={(includeNullValues) => setIncludeEncounterStartDateNull(includeNullValues)} - /> - - Fin de prise en charge - - setEncounterEndDate(newEndDate)} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - includeNullValues={includeEncounterEndDateNull} - onChangeIncludeNullValues={(includeNullValues) => setIncludeEncounterEndDateNull(includeNullValues)} - /> - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={priseEnChargeType} - onChange={(e, value) => setPriseEnChargeType(value)} - renderInput={(params) => } - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={typeDeSejour} - onChange={(e, value) => setTypeDeSejour(value)} - renderInput={(params) => } - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={encounterStatus} - onChange={(e, value) => setEncounterStatus(value)} - renderInput={(params) => } // TODO Mehdi - /> - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={admissionMode} - onChange={(e, value) => setAdmissionMode(value)} - renderInput={(params) => } - /> - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={admission} - onChange={(e, value) => setAdmission(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={entryMode} - onChange={(e, value) => setEntryMode(value)} - renderInput={(params) => } - /> - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={exitMode} - onChange={(e, value) => setExitMode(value)} - renderInput={(params) => } - /> - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={reason} - onChange={(e, value) => setReason(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={destination} - onChange={(e, value) => setDestination(value)} - renderInput={(params) => } - /> - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={provenance} - onChange={(e, value) => setProvenance(value)} - renderInput={(params) => } - /> - - - - {!isEdition && ( - - )} - - - - - - ) -} - -export default EncounterForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/EncounterForm/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/EncounterForm/styles.ts deleted file mode 100644 index 5b2ee8bb0..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/EncounterForm/styles.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - root: { - display: 'flex', - flexDirection: 'column', - flex: '1 1 auto' - }, - actionContainer: { - display: 'flex', - alignItems: 'center', - height: 72, - padding: 20, - backgroundColor: '#317EAA', - color: 'white' - }, - backButton: { color: 'white' }, - divider: { background: 'white' }, - titleLabel: { marginLeft: '1em' }, - formContainer: { - overflow: 'auto', - maxHeight: 'calc(100vh - 135px)' - }, - inputContainer: { - flexDirection: 'column', - padding: '1em' - }, - criteriaActionContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexWrap: 'wrap', - borderTop: '1px solid grey', - position: 'absolute', - width: '100%', - bottom: 0, - left: 0, - background: '#fff', - '& > button': { - margin: '12px 8px' - } - }, - inputItem: { - margin: '1em', - width: 'calc(100% - 2em)' - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/GhmForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/GhmForm.tsx deleted file mode 100644 index 247d8e2f4..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/GhmForm.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import React, { useState } from 'react' - -import { - Alert, - Autocomplete, - Button, - Divider, - FormLabel, - Grid, - IconButton, - Link, - Switch, - TextField, - Typography -} from '@mui/material' - -import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace' - -import useStyles from './styles' -import { useAppDispatch, useAppSelector } from 'state' -import { fetchClaim } from 'state/pmsi' -import { CriteriaItemDataCache, HierarchyTree } from 'types' -import AdvancedInputs from '../../../AdvancedInputs/AdvancedInputs' -import AsyncAutocomplete from 'components/ui/Inputs/AsyncAutocomplete' -import services from 'services/aphp' -import { Comparators, GhmDataType, SelectedCriteriaType } from 'types/requestCriterias' -import { BlockWrapper } from 'components/ui/Layout' -import OccurenceInput from 'components/ui/Inputs/Occurences' -import { SourceType } from 'types/scope' -import { Hierarchy } from 'types/hierarchy' - -type GHMFormProps = { - isOpen: boolean - isEdition: boolean - criteriaData: CriteriaItemDataCache - selectedCriteria: GhmDataType - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onChangeValue: (key: string, value: any) => void - goBack: () => void - onChangeSelectedCriteria: (data: SelectedCriteriaType) => void -} - -enum Error { - ADVANCED_INPUTS_ERROR, - NO_ERROR -} - -const GhmForm: React.FC = (props) => { - const { isOpen, isEdition, criteriaData, selectedCriteria, onChangeValue, onChangeSelectedCriteria, goBack } = props - - const { classes } = useStyles() - const dispatch = useAppDispatch() - const initialState: HierarchyTree | null = useAppSelector((state) => state.syncHierarchyTable) - const currentState = { ...selectedCriteria, ...initialState } - const [multiFields, setMultiFields] = useState(localStorage.getItem('multiple_fields')) - const [occurrence, setOccurrence] = useState(currentState.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - currentState.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [error, setError] = useState(Error.NO_ERROR) - - const getGhmOptions = async (searchValue: string, signal: AbortSignal) => - await services.cohortCreation.fetchGhmData(searchValue, false, signal) - const _onSubmit = () => { - onChangeSelectedCriteria({ ...currentState, occurrence: occurrence, occurrenceComparator: occurrenceComparator }) - dispatch(fetchClaim()) - } - const defaultValuesCode = currentState.code - ? currentState.code.map((code) => { - const criteriaCode = criteriaData.data.ghmData - ? criteriaData.data.ghmData.find((g: Hierarchy) => g.id === code.id) - : null - return { - id: code.id, - label: code.label ? code.label : criteriaCode?.label ?? '?' - } - }) - : [] - - return isOpen ? ( - - - {!isEdition ? ( - <> - - - - - Ajouter un critère de GHM - - ) : ( - Modifier un critère de GHM - )} - - - - {!multiFields && ( - { - localStorage.setItem('multiple_fields', 'ok') - setMultiFields('ok') - }} - > - Tous les éléments des champs multiples sont liés par une contrainte OU - - )} - - - Données actuellement disponibles : PMSI ORBIS. Pour plus d'informations sur les prochaines intégrations de - données, veuillez vous référer au tableau trimestriel de disponibilité des données disponible{' '} - - ici - - - - - GHM - - onChangeValue('title', e.target.value)} - /> - - - onChangeValue('isInclusive', !currentState.isInclusive)} - style={{ margin: 'auto 1em' }} - component="legend" - > - Exclure les patients qui suivent les règles suivantes - - onChangeValue('isInclusive', !event.target.checked)} - color="secondary" - /> - - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - withHierarchyInfo - /> - - - { - onChangeValue('code', value) - }} - /> - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={currentState.encounterStatus} - onChange={(e, value) => onChangeValue('encounterStatus', value)} - renderInput={(params) => } - /> - - setError(isError ? Error.ADVANCED_INPUTS_ERROR : Error.NO_ERROR)} - /> - - - - {!isEdition && ( - - )} - - - - - ) : ( - <> - ) -} - -export default GhmForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/styles.ts deleted file mode 100644 index 22ddd251b..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/styles.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - root: { - display: 'flex', - flexDirection: 'column', - flex: '1 1 auto' - }, - actionContainer: { - display: 'flex', - alignItems: 'center', - height: 72, - padding: 20, - backgroundColor: '#317EAA', - color: 'white', - //not default - marginBottom: 46 - }, - backButton: { color: 'white' }, - divider: { background: 'white' }, - titleLabel: { marginLeft: '1em' }, - formContainer: { - overflow: 'auto', - maxHeight: 'calc(100vh - 183px)' - }, - inputContainer: { - padding: '1em', - display: 'flex', - flex: '1 1 0%', - flexDirection: 'column' - }, - inputItem: { - margin: '1em', - width: 'calc(100% - 2em)' - }, - criteriaActionContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexWrap: 'wrap', - borderTop: '1px solid grey', - position: 'absolute', - width: '100%', - bottom: 0, - left: 0, - background: '#fff', - '& > button': { - margin: '12px 8px' - } - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Hierarchy/GhmHierarchy.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Hierarchy/GhmHierarchy.tsx index 02e4a7dc1..e98645014 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Hierarchy/GhmHierarchy.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Hierarchy/GhmHierarchy.tsx @@ -32,10 +32,10 @@ import { import useStyles from './styles' import { decrementLoadingSyncHierarchyTable, incrementLoadingSyncHierarchyTable } from 'state/syncHierarchyTable' import { findSelectedInListAndSubItems } from 'utils/cohortCreation' -import { defaultClaim } from '../../index' import { HierarchyTree } from 'types' -import { GhmDataType } from 'types/requestCriterias' import { Hierarchy } from 'types/hierarchy' +import { GhmDataType, form } from '../../../forms/GHMForm' +import { CriteriaType } from 'types/requestCriterias' type GhmListItemProps = { ghmItem: Hierarchy @@ -61,7 +61,7 @@ const GhmListItem: React.FC = (props) => { if (isLoadingsyncHierarchyTable > 0 || isLoadingPmsi > 0) return dispatch(incrementLoadingSyncHierarchyTable()) setOpen(!open) - const newHierarchy = await expandItem(ghmCode, selectedItems || [], ghmHierarchy, defaultClaim.type, dispatch) + const newHierarchy = await expandItem(ghmCode, selectedItems || [], ghmHierarchy, CriteriaType.CLAIM, dispatch) handleClick(selectedItems, newHierarchy) dispatch(decrementLoadingSyncHierarchyTable()) } diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/index.tsx index 88af99817..12b253095 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/index.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/index.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react' import { Tab, Tabs } from '@mui/material' -import GHMForm from './components/Form/GhmForm' import GHMHierarchy from './components/Hierarchy/GhmHierarchy' import useStyles from './styles' @@ -10,27 +9,18 @@ import { initSyncHierarchyTableEffect, syncOnChangeFormValue } from 'utils/pmsi' import { fetchClaim } from 'state/pmsi' import { EXPLORATION } from '../../../../../../../../constants' import { CriteriaDrawerComponentProps } from 'types' -import { Comparators, GhmDataType, CriteriaType } from 'types/requestCriterias' import { Hierarchy } from 'types/hierarchy' - -export const defaultClaim: Omit = { - type: CriteriaType.CLAIM, - title: 'Critères GHM', - code: [], - label: undefined, - occurrence: 1, - occurrenceComparator: Comparators.GREATER_OR_EQUAL, - startOccurrence: [null, null], - isInclusive: true, - encounterStartDate: [null, null], - encounterEndDate: [null, null], - encounterStatus: [] -} +import { form, GhmDataType } from '../forms/GHMForm' +import { CriteriaType } from 'types/requestCriterias' +import { fetchValueSet } from 'services/aphp/callApi' +import CriteriaForm from '../CriteriaForm' const Index = (props: CriteriaDrawerComponentProps) => { - const { criteriaData, selectedCriteria, onChangeSelectedCriteria, goBack } = props + const { selectedCriteria, onChangeSelectedCriteria, goBack } = props const [selectedTab, setSelectedTab] = useState<'form' | 'hierarchy'>(selectedCriteria ? 'form' : 'hierarchy') - const [defaultCriteria, setDefaultCriteria] = useState((selectedCriteria as GhmDataType) || defaultClaim) + const [defaultCriteria, setDefaultCriteria] = useState( + (selectedCriteria as GhmDataType) || { ...form().initialData } + ) const isEdition = selectedCriteria !== null const dispatch = useAppDispatch() @@ -47,7 +37,7 @@ const Index = (props: CriteriaDrawerComponentProps) => { } // eslint-disable-next-line @typescript-eslint/no-explicit-any const _onChangeFormValue = async (key: string, value: any, newHierarchy: Hierarchy[] = ghmHierarchy) => - await syncOnChangeFormValue(key, value, newHierarchy, setDefaultCriteria, selectedTab, defaultClaim.type, dispatch) + await syncOnChangeFormValue(key, value, newHierarchy, setDefaultCriteria, selectedTab, CriteriaType.CLAIM, dispatch) const _initSyncHierarchyTableEffect = async () => { await initSyncHierarchyTableEffect( @@ -55,7 +45,7 @@ const Index = (props: CriteriaDrawerComponentProps) => { selectedCriteria, defaultCriteria && defaultCriteria.code ? defaultCriteria.code : [], fetchClaim, - defaultClaim.type, + CriteriaType.CLAIM, dispatch ) } @@ -75,17 +65,21 @@ const Index = (props: CriteriaDrawerComponentProps) => { - { - + fetchValueSet( + codeSystemUrl, + { valueSetTitle: 'Toute la hiérarchie', search: code, noStar: false }, + abortSignal + ) + } /> - } + ) : null} { { - const criteria = selectedCriteria as HospitDataType - const [title, setTitle] = useState(criteria?.title || "Critère de Fiche d'hospitalisation") - const [hospitReason, setHospitReason] = useState(criteria?.hospitReason || '') - const [inUteroTransfer, setInUteroTransfer] = useState( - mappingCriteria(criteria?.inUteroTransfer, CriteriaDataKey.IN_UTERO_TRANSFER, criteriaData) || [] - ) - const [pregnancyMonitoring, setPregnancyMonitoring] = useState( - mappingCriteria(criteria?.pregnancyMonitoring, CriteriaDataKey.PREGNANCY_MONITORING, criteriaData) || [] - ) - const [vme, setVme] = useState(mappingCriteria(criteria?.vme, CriteriaDataKey.VME, criteriaData) || []) - const [maturationCorticotherapie, setMaturationCorticotherapie] = useState( - mappingCriteria(criteria?.maturationCorticotherapie, CriteriaDataKey.MATURATION_CORTICOTHERAPIE, criteriaData) || [] - ) - const [chirurgicalGesture, setChirurgicalGesture] = useState( - mappingCriteria(criteria?.chirurgicalGesture, CriteriaDataKey.CHIRURGICAL_GESTURE, criteriaData) || [] - ) - const [childbirth, setChildbirth] = useState( - mappingCriteria(criteria?.childbirth, CriteriaDataKey.CHILDBIRTH, criteriaData) || [] - ) - const [hospitalChildBirthPlace, setHospitalChildBirthPlace] = useState( - mappingCriteria(criteria?.hospitalChildBirthPlace, CriteriaDataKey.HOSPITALCHILDBIRTHPLACE, criteriaData) - ) - const [otherHospitalChildBirthPlace, setOtherHospitalChildBirthPlace] = useState( - mappingCriteria(criteria?.otherHospitalChildBirthPlace, CriteriaDataKey.OTHERHOSPITALCHILDBIRTHPLACE, criteriaData) - ) - const [homeChildBirthPlace, setHomeChildBirthPlace] = useState( - mappingCriteria(criteria?.homeChildBirthPlace, CriteriaDataKey.HOMECHILDBIRTHPLACE, criteriaData) - ) - const [childbirthMode, setChildbirthMode] = useState( - mappingCriteria(criteria?.childbirthMode, CriteriaDataKey.CHILDBIRTH_MODE, criteriaData) || [] - ) - const [maturationReason, setMaturationReason] = useState( - mappingCriteria(criteria?.maturationReason, CriteriaDataKey.MATURATION_REASON, criteriaData) || [] - ) - const [maturationModality, setMaturationModality] = useState( - mappingCriteria(criteria?.maturationModality, CriteriaDataKey.MATURATION_MODALITY, criteriaData) || [] - ) - const [imgIndication, setImgIndication] = useState( - mappingCriteria(criteria?.imgIndication, CriteriaDataKey.IMG_INDICATION, criteriaData) || [] - ) - const [laborOrCesareanEntry, setLaborOrCesareanEntry] = useState( - mappingCriteria(criteria?.laborOrCesareanEntry, CriteriaDataKey.LABOR_OR_CESAREAN_ENTRY, criteriaData) || [] - ) - const [pathologyDuringLabor, setPathologyDuringLabor] = useState( - mappingCriteria(criteria?.pathologyDuringLabor, CriteriaDataKey.PATHOLOGY_DURING_LABOR, criteriaData) || [] - ) - const [obstetricalGestureDuringLabor, setObstetricalGestureDuringLabor] = useState( - mappingCriteria( - criteria?.obstetricalGestureDuringLabor, - CriteriaDataKey.OBSTETRICAL_GESTURE_DURING_LABOR, - criteriaData - ) || [] - ) - const [analgesieType, setAnalgesieType] = useState( - mappingCriteria(criteria?.analgesieType, CriteriaDataKey.ANALGESIE_TYPE, criteriaData) || [] - ) - const [birthDeliveryStartDate, setBirthDeliveryStartDate] = useState( - criteria?.birthDeliveryStartDate || null - ) - const [birthDeliveryEndDate, setBirthDeliveryEndDate] = useState( - criteria?.birthDeliveryEndDate || null - ) - const [birthDeliveryWeeks, setBirthDeliveryWeeks] = useState(criteria?.birthDeliveryWeeks || 0) - const [birthDeliveryWeeksComparator, setBirthDeliveryWeeksComparator] = useState( - criteria?.birthDeliveryWeeksComparator || Comparators.GREATER_OR_EQUAL - ) - const [birthDeliveryDays, setBirthDeliveryDays] = useState(criteria?.birthDeliveryDays || 0) - const [birthDeliveryDaysComparator, setBirthDeliveryDaysComparator] = useState( - criteria?.birthDeliveryDaysComparator || Comparators.GREATER_OR_EQUAL - ) - const [birthDeliveryWay, setBirthDeliveryWay] = useState( - mappingCriteria(criteria?.birthDeliveryWay, CriteriaDataKey.BIRTH_DELIVERY_WAY, criteriaData) || [] - ) - const [instrumentType, setInstrumentType] = useState( - mappingCriteria(criteria?.instrumentType, CriteriaDataKey.INSTRUMENT_TYPE, criteriaData) || [] - ) - const [cSectionModality, setCSectionModality] = useState( - mappingCriteria(criteria?.cSectionModality, CriteriaDataKey.C_SECTION_MODALITY, criteriaData) || [] - ) - const [presentationAtDelivery, setPresentationAtDelivery] = useState( - mappingCriteria(criteria?.presentationAtDelivery, CriteriaDataKey.PRESENTATION_AT_DELIVERY, criteriaData) || [] - ) - const [birthMensurationsGrams, setBirthMensurationsGrams] = useState(criteria?.birthMensurationsGrams || 0) - const [birthMensurationsGramsComparator, setBirthMensurationsGramsComparator] = useState( - criteria?.birthMensurationsGramsComparator || Comparators.GREATER_OR_EQUAL - ) - const [birthMensurationsPercentil, setBirthMensurationsPercentil] = useState( - criteria?.birthMensurationsPercentil || 0 - ) - const [birthMensurationsPercentilComparator, setBirthMensurationsPercentilComparator] = useState( - criteria?.birthMensurationsPercentilComparator || Comparators.GREATER_OR_EQUAL - ) - const [apgar1, setApgar1] = useState(criteria?.apgar1 || 0) - const [apgar1Comparator, setApgar1Comparator] = useState( - criteria?.apgar1Comparator || Comparators.GREATER_OR_EQUAL - ) - const [apgar3, setApgar3] = useState(criteria?.apgar3 || 0) - const [apgar3Comparator, setApgar3Comparator] = useState( - criteria?.apgar3Comparator || Comparators.GREATER_OR_EQUAL - ) - const [apgar5, setApgar5] = useState(criteria?.apgar5 || 0) - const [apgar5Comparator, setApgar5Comparator] = useState( - criteria?.apgar5Comparator || Comparators.GREATER_OR_EQUAL - ) - const [apgar10, setApgar10] = useState(criteria?.apgar10 || 0) - const [apgar10Comparator, setApgar10Comparator] = useState( - criteria?.apgar10Comparator || Comparators.GREATER_OR_EQUAL - ) - const [arterialPhCord, setArterialPhCord] = useState(criteria?.arterialPhCord || 0) - const [arterialPhCordComparator, setArterialPhCordComparator] = useState( - criteria?.arterialPhCordComparator || Comparators.GREATER_OR_EQUAL - ) - const [arterialCordLactates, setArterialCordLactates] = useState(criteria?.arterialCordLactates || 0) - const [arterialCordLactatesComparator, setArterialCordLactatesComparator] = useState( - criteria?.arterialCordLactatesComparator || Comparators.GREATER_OR_EQUAL - ) - const [birthStatus, setBirthStatus] = useState( - mappingCriteria(criteria?.birthStatus, CriteriaDataKey.BIRTHSTATUS, criteriaData) || [] - ) - const [postpartumHemorrhage, setPostpartumHemorrhage] = useState( - mappingCriteria(criteria?.postpartumHemorrhage, CriteriaDataKey.POSTPARTUM_HEMORRHAGE, criteriaData) || [] - ) - const [conditionPerineum, setConditionPerineum] = useState( - mappingCriteria(criteria?.conditionPerineum, CriteriaDataKey.CONDITION_PERINEUM, criteriaData) || [] - ) - const [exitPlaceType, setExitPlaceType] = useState( - mappingCriteria(criteria?.exitPlaceType, CriteriaDataKey.EXIT_PLACE_TYPE, criteriaData) || [] - ) - const [feedingType, setFeedingType] = useState( - mappingCriteria(criteria?.feedingType, CriteriaDataKey.FEEDING_TYPE, criteriaData) || [] - ) - const [complication, setComplication] = useState( - mappingCriteria(criteria?.complication, CriteriaDataKey.COMPLICATION, criteriaData) || [] - ) - const [exitFeedingMode, setExitFeedingMode] = useState( - mappingCriteria(criteria?.exitFeedingMode, CriteriaDataKey.EXIT_FEEDING_MODE, criteriaData) || [] - ) - const [exitDiagnostic, setExitDiagnostic] = useState( - mappingCriteria(criteria?.exitDiagnostic, CriteriaDataKey.EXIT_DIAGNOSTIC, criteriaData) || [] - ) - const [encounterStatus, setEncounterStatus] = useState( - mappingCriteria(criteria?.encounterStatus, CriteriaDataKey.ENCOUNTER_STATUS, criteriaData) || [] - ) - const [encounterService, setEncounterService] = useState[]>( - criteria?.encounterService || [] - ) - const [occurrence, setOccurrence] = useState(criteria?.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - criteria?.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [isInclusive, setIsInclusive] = useState(criteria?.isInclusive || true) - - const { classes } = useStyles() - const isEdition = selectedCriteria !== null ? true : false - const [error, setError] = useState(Error.NO_ERROR) - - useEffect(() => { - setError(Error.NO_ERROR) - if ( - (occurrence === 0 && occurrenceComparator === Comparators.EQUAL) || - (occurrence === 1 && occurrenceComparator === Comparators.LESS) || - (occurrence === 0 && occurrenceComparator === Comparators.LESS_OR_EQUAL) - ) { - setError(Error.EMPTY_FORM) - } - }, [occurrence, occurrenceComparator]) - - const onSubmit = () => { - onChangeSelectedCriteria({ - type: CriteriaType.HOSPIT, - id: criteria?.id, - hospitReason, - inUteroTransfer, - pregnancyMonitoring, - vme, - maturationCorticotherapie, - chirurgicalGesture, - childbirth, - hospitalChildBirthPlace, - otherHospitalChildBirthPlace, - homeChildBirthPlace, - childbirthMode, - maturationReason, - maturationModality, - imgIndication, - laborOrCesareanEntry, - pathologyDuringLabor, - obstetricalGestureDuringLabor, - analgesieType, - birthDeliveryStartDate, - birthDeliveryEndDate, - birthDeliveryWeeks, - birthDeliveryWeeksComparator, - birthDeliveryDays, - birthDeliveryDaysComparator, - birthDeliveryWay, - instrumentType, - cSectionModality, - presentationAtDelivery, - birthMensurationsGrams, - birthMensurationsGramsComparator, - birthMensurationsPercentil, - birthMensurationsPercentilComparator, - apgar1, - apgar1Comparator, - apgar3, - apgar3Comparator, - apgar5, - apgar5Comparator, - apgar10, - apgar10Comparator, - arterialPhCord, - arterialPhCordComparator, - arterialCordLactates, - arterialCordLactatesComparator, - birthStatus, - postpartumHemorrhage, - conditionPerineum, - exitPlaceType, - feedingType, - complication, - exitFeedingMode, - exitDiagnostic, - encounterService, - occurrence, - occurrenceComparator, - encounterStatus, - isInclusive, - startOccurrence: [null, null], - title - }) - } - return ( - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - /> - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={encounterStatus} - onChange={(e, value) => setEncounterStatus(value)} - renderInput={(params) => } - /> - - - - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={inUteroTransfer} - onChange={(e, value) => setInUteroTransfer(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={pregnancyMonitoring} - onChange={(e, value) => setPregnancyMonitoring(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={vme} - onChange={(e, value) => setVme(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={maturationCorticotherapie} - onChange={(e, value) => setMaturationCorticotherapie(value)} - renderInput={(params) => } - /> - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={chirurgicalGesture} - onChange={(e, value) => setChirurgicalGesture(value)} - renderInput={(params) => } - /> - - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={childbirth} - onChange={(e, value) => setChildbirth(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={hospitalChildBirthPlace} - onChange={(e, value) => setHospitalChildBirthPlace(value)} - renderInput={(params) => ( - - )} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={otherHospitalChildBirthPlace} - onChange={(e, value) => setOtherHospitalChildBirthPlace(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={homeChildBirthPlace} - onChange={(e, value) => setHomeChildBirthPlace(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={childbirthMode} - onChange={(e, value) => setChildbirthMode(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={maturationReason} - onChange={(e, value) => setMaturationReason(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={maturationModality} - onChange={(e, value) => setMaturationModality(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={imgIndication} - onChange={(e, value) => setImgIndication(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={laborOrCesareanEntry} - onChange={(e, value) => setLaborOrCesareanEntry(value)} - renderInput={(params) => ( - - )} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={pathologyDuringLabor} - onChange={(e, value) => setPathologyDuringLabor(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={obstetricalGestureDuringLabor} - onChange={(e, value) => setObstetricalGestureDuringLabor(value)} - renderInput={(params) => ( - - )} - /> - - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={analgesieType} - onChange={(e, value) => setAnalgesieType(value)} - renderInput={(params) => } - /> - - - - - - - - { - setBirthDeliveryStartDate(start) - setBirthDeliveryEndDate(end) - }} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - /> - - - - { - setBirthDeliveryWeeks(newBirthdaydeliveryWeeks) - setBirthDeliveryWeeksComparator(newComparator) - }} - /> - - - - { - setBirthDeliveryDays(newbirthDeliveryDays) - setBirthDeliveryDaysComparator(newComparator) - }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={birthDeliveryWay} - onChange={(e, value) => setBirthDeliveryWay(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={instrumentType} - onChange={(e, value) => setInstrumentType(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={cSectionModality} - onChange={(e, value) => setCSectionModality(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={presentationAtDelivery} - onChange={(e, value) => setPresentationAtDelivery(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - { - setBirthMensurationsGrams(newBirthMensurations) - setBirthMensurationsGramsComparator(newComparator) - }} - /> - - - - - { - setBirthMensurationsPercentil(newbirthMensurationsPercentil) - setBirthMensurationsPercentilComparator(newComparator) - }} - /> - - - - { - setApgar1(newapgar1) - setApgar1Comparator(newComparator) - }} - /> - - - - { - setApgar3(newapgar3) - setApgar3Comparator(newComparator) - }} - /> - - - - { - setApgar5(newapgar5) - setApgar5Comparator(newComparator) - }} - /> - - - - { - setApgar10(newapgar10) - setApgar10Comparator(newComparator) - }} - /> - - - - - { - setArterialPhCord(newarterialPhCord) - setArterialPhCordComparator(newComparator) - }} - /> - - - - - { - setArterialCordLactates(newarterialCordLactates) - setArterialCordLactatesComparator(newComparator) - }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={birthStatus} - onChange={(e, value) => setBirthStatus(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={postpartumHemorrhage} - onChange={(e, value) => setPostpartumHemorrhage(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={conditionPerineum} - onChange={(e, value) => setConditionPerineum(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={exitPlaceType} - onChange={(e, value) => setExitPlaceType(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={feedingType} - onChange={(e, value) => setFeedingType(value)} - renderInput={(params) => } - /> - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={complication} - onChange={(e, value) => setComplication(value)} - renderInput={(params) => } - /> - - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={exitFeedingMode} - onChange={(e, value) => setExitFeedingMode(value)} - renderInput={(params) => } - /> - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={exitDiagnostic} - onChange={(e, value) => setExitDiagnostic(value)} - renderInput={(params) => } - /> - - - - ) -} - -export default HospitForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/styles.ts deleted file mode 100644 index 84150bde6..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/styles.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - inputItem: { - margin: '1em' - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/IPPForm/IPPForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/IPPForm/IPPForm.tsx deleted file mode 100644 index 60a7e8068..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/IPPForm/IPPForm.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import React, { useEffect, useState } from 'react' - -import { Alert, Button, Divider, FormLabel, Grid, IconButton, Switch, Typography, TextField } from '@mui/material' - -import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace' - -import { CriteriaDrawerComponentProps } from 'types' - -import useStyles from './styles' -import { IPPListDataType, CriteriaType } from 'types/requestCriterias' - -const defaultIPPList: Omit = { - title: "Liste d'IPP", - type: CriteriaType.IPP_LIST, - search: '', - isInclusive: true -} - -const IPPForm: React.FC = (props) => { - const { selectedCriteria, goBack, onChangeSelectedCriteria } = props - - const { classes } = useStyles() - - const [error, setError] = useState(false) - const [defaultValues, setDefaultValues] = useState( - (selectedCriteria as IPPListDataType) || defaultIPPList - ) - const [ippList, setIppList] = useState([]) - - const isEdition = selectedCriteria !== null - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const _onChangeValue = (key: string, value: any) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const _defaultValues: any = defaultValues ? { ...defaultValues } : {} - _defaultValues[key] = value - setDefaultValues(_defaultValues as IPPListDataType) - } - - const _onSubmit = () => { - if (defaultValues && defaultValues.search?.length === 0) { - return setError(true) - } - - const _defaultValues = { ...defaultValues, search: ippList.join() } - onChangeSelectedCriteria(_defaultValues) - } - - useEffect(() => { - const ippMatches = (defaultValues.search || '').matchAll(/(:^|\D+)?(8\d{9})(?:$|\D+)/gm) || [] - const ippList = [] - - for (const match of ippMatches) { - ippList.push(match[1]) - } - - setIppList(ippList) - - if (ippList.length > 0) { - setError(false) - } else { - setError(true) - } - }, [defaultValues.search]) - - return ( - - - {!isEdition ? ( - <> - - - - - Ajouter un critère de liste d'IPP - - ) : ( - Modifier un critère de liste d'IPP - )} - - - - {error && Merci de renseigner au moins un IPP} - - - Liste d'IPP - - _onChangeValue('title', e.target.value)} - /> - - - _onChangeValue('isInclusive', !defaultValues.isInclusive)} - style={{ margin: 'auto 1em' }} - component="legend" - > - Exclure les patients qui suivent les règles suivantes - - _onChangeValue('isInclusive', !event.target.checked)} - color="secondary" - /> - - - - {ippList.length} IPP détecté{ippList.length > 1 ? 's' : ''}. - - - - _onChangeValue('search', event.target.value)} - multiline - minRows={5} - style={{ width: '100%' }} - value={defaultValues.search} - /> - - - - - {!isEdition && ( - - )} - - - - - ) -} - -export default IPPForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/IPPForm/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/IPPForm/styles.ts deleted file mode 100644 index 9e7a8bf6b..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/IPPForm/styles.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - root: { - display: 'flex', - flexDirection: 'column', - flex: '1 1 auto' - }, - actionContainer: { - display: 'flex', - alignItems: 'center', - height: 72, - padding: 20, - backgroundColor: '#317EAA', - color: 'white' - }, - backButton: { color: 'white' }, - divider: { background: 'white' }, - titleLabel: { marginLeft: '1em' }, - formContainer: { - overflow: 'auto', - maxHeight: 'calc(100vh - 135px)' - }, - inputContainer: { - padding: '1em', - display: 'flex', - flex: '1 1 0%', - flexDirection: 'column' - }, - inputItem: { - margin: '1em', - width: 'calc(100% - 2em)' - }, - criteriaActionContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexWrap: 'wrap', - borderTop: '1px solid grey', - position: 'absolute', - width: '100%', - bottom: 0, - left: 0, - background: '#fff', - '& > button': { - margin: '12px 8px' - } - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/index.tsx deleted file mode 100644 index d228fcd45..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/index.tsx +++ /dev/null @@ -1,422 +0,0 @@ -import React, { useContext, useState } from 'react' -import { Autocomplete, Grid, MenuItem, Select, TextField } from '@mui/material' -import { BlockWrapper } from 'components/ui/Layout' -import CalendarRange from 'components/ui/Inputs/CalendarRange' -import Collapse from 'components/ui/Collapse' -import { DurationUnitWrapper } from 'components/ui/Inputs/DurationRange/styles' -import { TextFieldWrapper } from 'components/ui/Inputs/DurationRange/styles' -import OccurenceInput from 'components/ui/Inputs/Occurences' -import CriteriaLayout from 'components/ui/CriteriaLayout' -import AdvancedInputs from '../AdvancedInputs/AdvancedInputs' - -import { CriteriaDrawerComponentProps } from 'types' -import { CalendarRequestLabel } from 'types/dates' -import { Comparators, CriteriaDataKey, ImagingDataType, CriteriaType, CriteriaTypeLabels } from 'types/requestCriterias' -import { - DocumentAttachmentMethod, - DocumentAttachmentMethodLabel, - DurationRangeType, - LabelObject -} from 'types/searchCriterias' -import useStyles from './styles' -import { mappingCriteria } from '../DemographicForm' -import SearchbarWithCheck from 'components/ui/Inputs/SearchbarWithCheck' -import UidTextfield from 'components/ui/Inputs/UidTextfield' -import { SourceType } from 'types/scope' -import { CriteriaLabel } from 'components/ui/CriteriaLabel' -import { AppConfig } from 'config' - -enum Error { - INCOHERENT_AGE_ERROR, - SEARCHINPUT_ERROR, - UID_ERROR, - NO_ERROR, - ADVANCED_INPUTS_ERROR -} - -export const withDocumentOptions = [ - { - id: DocumentAttachmentMethod.NONE, - label: DocumentAttachmentMethodLabel.NONE - }, - { - id: DocumentAttachmentMethod.ACCESS_NUMBER, - label: DocumentAttachmentMethodLabel.ACCESS_NUMBER - }, - { - id: DocumentAttachmentMethod.INFERENCE_TEMPOREL, - label: DocumentAttachmentMethodLabel.INFERENCE_TEMPOREL - } -] - -const ImagingForm: React.FC = (props) => { - const { classes } = useStyles() - const appConfig = useContext(AppConfig) - const { criteriaData, onChangeSelectedCriteria, goBack } = props - const selectedCriteria = props.selectedCriteria as ImagingDataType - const isEdition = selectedCriteria !== null ? true : false - const [title, setTitle] = useState(selectedCriteria?.title || "Critère d'Imagerie") - const [occurrence, setOccurrence] = useState(selectedCriteria?.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - selectedCriteria?.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [isInclusive, setIsInclusive] = useState( - selectedCriteria?.isInclusive === undefined ? true : selectedCriteria?.isInclusive - ) - const [studyStartDate, setStudyStartDate] = useState(selectedCriteria?.studyStartDate || null) - const [studyEndDate, setStudyEndDate] = useState(selectedCriteria?.studyEndDate || null) - const [studyModalities, setStudyModalities] = useState( - mappingCriteria(selectedCriteria?.studyModalities, CriteriaDataKey.MODALITIES, criteriaData) || [] - ) - const [studyDescription, setStudyDescription] = useState(selectedCriteria?.studyDescription || '') - const [studyProcedure, setStudyProcedure] = useState(selectedCriteria?.studyProcedure || '') - const [numberOfSeries, setNumberOfSeries] = useState(selectedCriteria?.numberOfSeries || 1) - const [seriesComparator, setSeriesComparator] = useState( - selectedCriteria?.seriesComparator || Comparators.GREATER_OR_EQUAL - ) - const [numberOfIns, setNumberOfIns] = useState(selectedCriteria?.numberOfIns || 1) - const [instancesComparator, setInstancesComparator] = useState( - selectedCriteria?.instancesComparator || Comparators.GREATER_OR_EQUAL - ) - const [withDocument, setWithDocument] = useState( - selectedCriteria?.withDocument || DocumentAttachmentMethod.NONE - ) - const [daysOfDelay, setDaysOfDelay] = useState(selectedCriteria?.daysOfDelay || null) - const [studyUid, setStudyUid] = useState(selectedCriteria?.studyUid || '') - - const [seriesStartDate, setSeriesStartDate] = useState(selectedCriteria?.seriesStartDate || null) - const [seriesEndDate, setSeriesEndDate] = useState(selectedCriteria?.seriesEndDate || null) - const [seriesDescription, setSeriesDescription] = useState(selectedCriteria?.seriesDescription || '') - const [seriesProtocol, setSeriesProtocol] = useState(selectedCriteria?.seriesProtocol || '') - const [seriesModalities, setSeriesModalities] = useState( - mappingCriteria(selectedCriteria?.seriesModalities, CriteriaDataKey.MODALITIES, criteriaData) || [] - ) - const [seriesUid, setSeriesUid] = useState(selectedCriteria?.seriesUid || '') - const [encounterService, setEncounterService] = useState(selectedCriteria?.encounterService || []) - const [encounterStartDate, setEncounterStartDate] = useState( - selectedCriteria?.encounterStartDate || [null, null] - ) - const [includeEncounterStartDateNull, setIncludeEncounterStartDateNull] = useState( - selectedCriteria?.includeEncounterStartDateNull - ) - const [encounterEndDate, setEncounterEndDate] = useState( - selectedCriteria?.encounterEndDate || [null, null] - ) - const [includeEncounterEndDateNull, setIncludeEncounterEndDateNull] = useState( - selectedCriteria?.includeEncounterEndDateNull - ) - const [encounterStatus, setEncounterStatus] = useState( - mappingCriteria(selectedCriteria?.encounterStatus, CriteriaDataKey.ENCOUNTER_STATUS, criteriaData) || [] - ) - - const [error, setError] = useState(Error.NO_ERROR) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const _onChangeValue = (key: string, value: any) => { - switch (key) { - case 'encounterService': - setEncounterService(value) - break - case 'encounterStartDate': - setEncounterStartDate(value) - break - case 'encounterEndDate': - setEncounterEndDate(value) - break - case 'includeEncounterStartDateNull': - setIncludeEncounterStartDateNull(value) - break - case 'includeEncounterEndDateNull': - setIncludeEncounterEndDateNull(value) - break - default: - break - } - } - - const onSubmit = () => { - onChangeSelectedCriteria({ - id: selectedCriteria?.id, - type: CriteriaType.IMAGING, - title, - isInclusive, - occurrence, - occurrenceComparator, - studyStartDate, - studyEndDate, - studyModalities, - studyDescription, - studyProcedure, - numberOfSeries, - seriesComparator, - numberOfIns, - instancesComparator, - withDocument, - daysOfDelay, - studyUid, - seriesStartDate, - seriesEndDate, - seriesDescription, - seriesProtocol, - seriesModalities, - seriesUid, - encounterService, - encounterStartDate, - includeEncounterStartDateNull, - encounterEndDate, - includeEncounterEndDateNull, - encounterStatus, - startOccurrence: [null, null] - }) - } - - const isSeriesUsed = - !!seriesStartDate || - !!seriesEndDate || - !!seriesDescription || - !!seriesProtocol || - seriesModalities.length > 0 || - !!seriesUid - - return ( - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={encounterStatus} - onChange={(e, value) => setEncounterStatus(value)} - renderInput={(params) => } - /> - - - {/* critères de study : */} - - - - - { - setStudyStartDate(start || null) - setStudyEndDate(end || null) - }} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={studyModalities} - onChange={(e, value) => setStudyModalities(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - - { - setNumberOfSeries(newOccurence) - setSeriesComparator(newComparator) - }} - /> - - - - { - setNumberOfIns(newOccurence) - setInstancesComparator(newComparator) - }} - /> - - - - - - - - - {withDocument === DocumentAttachmentMethod.INFERENCE_TEMPOREL && ( - - - - Plage de - - - setDaysOfDelay(event.target.value)} - type="number" - InputProps={{ - inputProps: { - min: 0 - } - }} - /> - - - {CalendarRequestLabel.DAY} - - - - )} - - - - - setError(isError ? Error.UID_ERROR : Error.NO_ERROR)} - /> - - - {/*critères de série : */} - - - - - { - setSeriesStartDate(start || null) - setSeriesEndDate(end || null) - }} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - /> - - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={seriesModalities} - onChange={(e, value) => setSeriesModalities(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - setError(isError ? Error.UID_ERROR : Error.NO_ERROR)} - /> - - - - - setError(isError ? Error.ADVANCED_INPUTS_ERROR : Error.NO_ERROR)} - onChangeValue={_onChangeValue} - /> - - ) -} - -export default ImagingForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/styles.ts deleted file mode 100644 index efecb7e1c..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/styles.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - inputContainer: { - padding: '1em', - display: 'flex', - flex: '1 1 0%', - flexDirection: 'column' - }, - inputItem: { - margin: '1em', - width: 'calc(100% - 2em)' - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Form/MedicationForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Form/MedicationForm.tsx deleted file mode 100644 index b853be09b..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Form/MedicationForm.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import React, { useEffect, useState } from 'react' - -import { - Alert, - Autocomplete, - Button, - Divider, - FormControlLabel, - FormLabel, - Grid, - IconButton, - Radio, - RadioGroup, - Switch, - TextField, - Typography -} from '@mui/material' - -import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace' - -import AdvancedInputs from '../../../AdvancedInputs/AdvancedInputs' - -import useStyles from './styles' -import { useAppDispatch, useAppSelector } from 'state' -import { fetchMedication } from 'state/medication' -import { CriteriaItemDataCache, HierarchyElementWithSystem, HierarchyTree } from 'types' -import AsyncAutocomplete from 'components/ui/Inputs/AsyncAutocomplete' -import services from 'services/aphp' -import { Comparators, CriteriaType, MedicationDataType, SelectedCriteriaType } from 'types/requestCriterias' -import { displaySystem } from 'utils/displayValueSetSystem' -import { BlockWrapper } from 'components/ui/Layout' -import OccurenceInput from 'components/ui/Inputs/Occurences' -import { SourceType } from 'types/scope' -import { Hierarchy } from 'types/hierarchy' - -type MedicationFormProps = { - isOpen: boolean - isEdition: boolean - criteriaData: CriteriaItemDataCache - selectedCriteria: MedicationDataType - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onChangeValue: (key: string, value: any) => void - goBack: () => void - onChangeSelectedCriteria: (data: SelectedCriteriaType) => void -} - -enum Error { - ADVANCED_INPUTS_ERROR, - NO_ERROR -} - -const MedicationForm: React.FC = (props) => { - const { isOpen, isEdition, criteriaData, selectedCriteria, onChangeValue, onChangeSelectedCriteria, goBack } = props - - const { classes } = useStyles() - const dispatch = useAppDispatch() - - const initialState: HierarchyTree | null = useAppSelector((state) => state.syncHierarchyTable) - const currentState = { ...selectedCriteria, ...initialState } - const [multiFields, setMultiFields] = useState(localStorage.getItem('multiple_fields')) - const [occurrence, setOccurrence] = useState(currentState.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - currentState.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [error, setError] = useState(Error.NO_ERROR) - - useEffect(() => { - if (currentState.type === CriteriaType.MEDICATION_ADMINISTRATION) { - onChangeValue('endOccurrence', [null, null]) - } - }, [currentState.type]) - - const getMedicationOptions = async (searchValue: string, signal: AbortSignal) => { - const response = await services.cohortCreation.fetchMedicationData(searchValue, false, signal) - return response.map((elem) => { - return { ...elem, label: displaySystem(elem.system) + elem.label } - }) - } - - const _onSubmit = () => { - onChangeSelectedCriteria({ ...currentState, occurrence: occurrence, occurrenceComparator: occurrenceComparator }) - dispatch(fetchMedication()) - } - - if (criteriaData?.data?.prescriptionTypes === 'loading' || criteriaData?.data?.administrations === 'loading') { - return <> - } - - const selectedCriteriaPrescriptionType = - currentState.type === CriteriaType.MEDICATION_REQUEST && currentState.prescriptionType - ? currentState.prescriptionType.map((prescriptionType) => { - const criteriaPrescriptionType = criteriaData.data.prescriptionTypes - ? criteriaData.data.prescriptionTypes.find((p: Hierarchy) => p.id === prescriptionType.id) - : null - return { - id: prescriptionType.id, - label: prescriptionType.label ? prescriptionType.label : criteriaPrescriptionType?.label ?? '?' - } - }) - : [] - - const selectedCriteriaAdministration = currentState.administration - ? currentState.administration.map((administration) => { - const criteriaAdministration = criteriaData.data.administrations - ? criteriaData.data.administrations.find((p: Hierarchy) => p.id === administration.id) - : null - return { - id: administration.id, - label: administration.label ? administration.label : criteriaAdministration?.label ?? '?' - } - }) - : [] - - const defaultValuesCode = currentState.code - ? currentState.code.map((code: HierarchyElementWithSystem) => { - const criteriaCode = criteriaData.data.medicationData - ? criteriaData.data.medicationData.find((g: HierarchyElementWithSystem) => g.id === code.id) - : null - return { - id: code.id, - label: code.label ? code.label : criteriaCode?.label ?? '?', - system: code.system ? code.system : criteriaCode?.system ?? '?' - } - }) - : [] - - return isOpen ? ( - - - {!isEdition ? ( - <> - - - - - Ajouter un critère de médicament - - ) : ( - Modifier un critère de médicament - )} - - - - {!multiFields && ( - { - localStorage.setItem('multiple_fields', 'ok') - setMultiFields('ok') - }} - > - Tous les éléments des champs multiples sont liés par une contrainte OU - - )} - - - Médicaments - onChangeValue('title', e.target.value)} - /> - - onChangeValue('isInclusive', !currentState.isInclusive)} - style={{ margin: 'auto 1em' }} - component="legend" - > - Exclure les patients qui suivent les règles suivantes - - onChangeValue('isInclusive', !event.target.checked)} - color="secondary" - /> - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - withHierarchyInfo - /> - - onChangeValue('type', value)} - > - } label="Prescription" /> - } - label="Administration" - /> - - - { - onChangeValue('code', value) - }} - /> - {currentState.type === CriteriaType.MEDICATION_REQUEST && ( - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={selectedCriteriaPrescriptionType} - onChange={(e, value) => onChangeValue('prescriptionType', value)} - renderInput={(params) => } - /> - )} - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={selectedCriteriaAdministration} - onChange={(e, value) => onChangeValue('administration', value)} - renderInput={(params) => } - /> - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={currentState.encounterStatus} - onChange={(e, value) => onChangeValue('encounterStatus', value)} - renderInput={(params) => } - /> - setError(isError ? Error.ADVANCED_INPUTS_ERROR : Error.NO_ERROR)} - /> - - - - {!isEdition && ( - - )} - - - - - ) : ( - <> - ) -} - -export default MedicationForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Form/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Form/styles.ts deleted file mode 100644 index d26f1b494..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Form/styles.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - root: { - display: 'flex', - flexDirection: 'column', - flex: '1 1 auto' - }, - actionContainer: { - display: 'flex', - alignItems: 'center', - height: 72, - padding: 20, - backgroundColor: '#317EAA', - color: 'white', - // not default - marginBottom: 46 - }, - backButton: { color: 'white' }, - divider: { background: 'white' }, - titleLabel: { marginLeft: '1em' }, - formContainer: { - overflow: 'auto', - maxHeight: 'calc(100vh - 183px)' - }, - inputContainer: { - padding: '1em', - display: 'flex', - flex: '1 1 0%', - flexDirection: 'column' - }, - inputItem: { - margin: '1em', - width: 'calc(100% - 2em)' - }, - criteriaActionContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexWrap: 'wrap', - borderTop: '1px solid grey', - position: 'absolute', - width: '100%', - bottom: 0, - left: 0, - background: '#fff', - '& > button': { - margin: '12px 8px' - } - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Hierarchy/MedicationHierarchy.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Hierarchy/MedicationHierarchy.tsx index cc2d793aa..81582fa32 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Hierarchy/MedicationHierarchy.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/components/Hierarchy/MedicationHierarchy.tsx @@ -35,10 +35,10 @@ import { import useStyles from './styles' import { findSelectedInListAndSubItems } from 'utils/cohortCreation' import { decrementLoadingSyncHierarchyTable, incrementLoadingSyncHierarchyTable } from 'state/syncHierarchyTable' -import { defaultMedication } from '../../index' import { HierarchyTree } from 'types' -import { MedicationDataType } from 'types/requestCriterias' import { Hierarchy } from 'types/hierarchy' +import { MedicationDataType } from '../../../forms/MedicationForm' +import { CriteriaType } from 'types/requestCriterias' type MedicationListItemProps = { medicationItem: Hierarchy @@ -76,7 +76,7 @@ const MedicationListItem: React.FC = (props) => { medicationCode, selectedItems || [], medicationHierarchy, - defaultMedication.type, + CriteriaType.MEDICATION_REQUEST, dispatch ) handleClick(selectedItems, newHierarchy) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/index.tsx index dee1c6f4e..472f711c2 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/index.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm/index.tsx @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react' import { Tabs, Tab } from '@mui/material' -import MedicationForm from './components/Form/MedicationForm' import MedicationExploration from './components/Hierarchy/MedicationHierarchy' import { CriteriaDrawerComponentProps } from 'types' @@ -11,29 +10,17 @@ import { useAppDispatch, useAppSelector } from 'state' import { initSyncHierarchyTableEffect, syncOnChangeFormValue } from 'utils/pmsi' import { fetchMedication } from 'state/medication' import { EXPLORATION } from '../../../../../../../../constants' -import { Comparators, MedicationDataType, CriteriaType } from 'types/requestCriterias' import { Hierarchy } from 'types/hierarchy' - -export const defaultMedication: Omit = { - type: CriteriaType.MEDICATION_REQUEST, - title: 'Critère de médicament', - code: [], - administration: [], - occurrence: 1, - occurrenceComparator: Comparators.GREATER_OR_EQUAL, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterEndDate: [null, null], - encounterStartDate: [null, null], - isInclusive: true, - encounterStatus: [] -} +import { MedicationDataType, form } from '../forms/MedicationForm' +import { CriteriaType } from 'types/requestCriterias' +import CriteriaForm from '../CriteriaForm' +import { fetchValueSet } from 'services/aphp/callApi' const Index = (props: CriteriaDrawerComponentProps) => { - const { criteriaData, selectedCriteria, onChangeSelectedCriteria, goBack } = props + const { selectedCriteria, onChangeSelectedCriteria, goBack } = props const [selectedTab, setSelectedTab] = useState<'form' | 'exploration'>(selectedCriteria ? 'form' : 'exploration') const [defaultCriteria, setDefaultCriteria] = useState( - (selectedCriteria as MedicationDataType) || defaultMedication + (selectedCriteria as MedicationDataType) || { ...form().initialData } ) const isEdition = selectedCriteria !== null @@ -50,7 +37,15 @@ const Index = (props: CriteriaDrawerComponentProps) => { } // eslint-disable-next-line @typescript-eslint/no-explicit-any const _onChangeFormValue = (key: string, value: any, hierarchy: Hierarchy[] = medicationHierarchy) => - syncOnChangeFormValue(key, value, hierarchy, setDefaultCriteria, selectedTab, defaultMedication.type, dispatch) + syncOnChangeFormValue( + key, + value, + hierarchy, + setDefaultCriteria, + selectedTab, + CriteriaType.MEDICATION_REQUEST, + dispatch + ) const _initSyncHierarchyTableEffect = async () => { await initSyncHierarchyTableEffect( @@ -58,7 +53,7 @@ const Index = (props: CriteriaDrawerComponentProps) => { selectedCriteria, defaultCriteria && defaultCriteria.code ? defaultCriteria.code : [], fetchMedication, - defaultMedication.type, + CriteriaType.MEDICATION_REQUEST, dispatch ) } @@ -78,17 +73,21 @@ const Index = (props: CriteriaDrawerComponentProps) => { - { - + fetchValueSet( + codeSystemUrl, + { valueSetTitle: 'Toute la hiérarchie', search: code, noStar: false }, + abortSignal + ) + } /> - } + )} { { - const criteria = selectedCriteria as PregnancyDataType - const [title, setTitle] = useState(criteria?.title || 'Critère de Fiche de Grossesse') - const [pregnancyStartDate, setPregnancyStartDate] = useState( - criteria?.pregnancyStartDate || null - ) - const [pregnancyEndDate, setPregnancyEndDate] = useState( - criteria?.pregnancyEndDate || null - ) - const [pregnancyMode, setPregnancyMode] = useState( - mappingCriteria(criteria?.pregnancyMode, CriteriaDataKey.PREGNANCY_MODE, criteriaData) || [] - ) - const [foetus, setFoetus] = useState(criteria?.foetus || 0) - const [foetusComparator, setFoetusComparator] = useState( - criteria?.foetusComparator || Comparators.GREATER_OR_EQUAL - ) - const [parity, setParity] = useState(criteria?.parity || 0) - const [parityComparator, setParityComparator] = useState( - criteria?.parityComparator || Comparators.GREATER_OR_EQUAL - ) - const [maternalRisks, setMaternalRisks] = useState( - mappingCriteria(criteria?.maternalRisks, CriteriaDataKey.MATERNAL_RISKS, criteriaData) || [] - ) - const [maternalRisksPrecision, setMaternalRisksPrecision] = useState(criteria?.maternalRisksPrecision || '') - const [risksRelatedToObstetricHistory, setRisksRelatedToObstetricHistory] = useState( - mappingCriteria( - criteria?.risksRelatedToObstetricHistory, - CriteriaDataKey.RISKS_RELATED_TO_OBSTETRIC_HISTORY, - criteriaData - ) || [] - ) - const [risksRelatedToObstetricHistoryPrecision, setRisksRelatedToObstetricHistoryPrecision] = useState( - criteria?.risksRelatedToObstetricHistoryPrecision || '' - ) - const [risksOrComplicationsOfPregnancy, setRisksOrComplicationsOfPregnancy] = useState( - mappingCriteria( - criteria?.risksOrComplicationsOfPregnancy, - CriteriaDataKey.RISKS_OR_COMPLICATIONS_OF_PREGNANCY, - criteriaData - ) || [] - ) - const [risksOrComplicationsOfPregnancyPrecision, setrisksOrComplicationsOfPregnancyPrecision] = useState( - criteria?.risksOrComplicationsOfPregnancyPrecision || '' - ) - const [corticotherapie, setCorticotherapie] = useState( - mappingCriteria(criteria?.corticotherapie, CriteriaDataKey.CORTICOTHERAPIE, criteriaData) || [] - ) - const [prenatalDiagnosis, setPrenatalDiagnosis] = useState( - mappingCriteria(criteria?.prenatalDiagnosis, CriteriaDataKey.PRENATAL_DIAGNOSIS, criteriaData) || [] - ) - const [ultrasoundMonitoring, setUltrasoundMonitoring] = useState( - mappingCriteria(criteria?.ultrasoundMonitoring, CriteriaDataKey.ULTRASOUND_MONITORING, criteriaData) || [] - ) - const [encounterService, setEncounterService] = useState[]>( - criteria?.encounterService || [] - ) - const [occurrence, setOccurrence] = useState(criteria?.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - criteria?.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [encounterStatus, setEncounterStatus] = useState( - mappingCriteria(criteria?.encounterStatus, CriteriaDataKey.ENCOUNTER_STATUS, criteriaData) || [] - ) - const [isInclusive, setIsInclusive] = useState(criteria?.isInclusive || true) - - const { classes } = useStyles() - const isEdition = selectedCriteria !== null ? true : false - const [error, setError] = useState(Error.NO_ERROR) - - useEffect(() => { - setError(Error.NO_ERROR) - if ( - (occurrence === 0 && occurrenceComparator === Comparators.EQUAL) || - (occurrence === 1 && occurrenceComparator === Comparators.LESS) || - (occurrence === 0 && occurrenceComparator === Comparators.LESS_OR_EQUAL) - ) { - setError(Error.EMPTY_FORM) - } - }, [occurrence, occurrenceComparator]) - - const onSubmit = () => { - onChangeSelectedCriteria({ - type: CriteriaType.PREGNANCY, - id: criteria?.id, - pregnancyStartDate, - pregnancyEndDate, - pregnancyMode, - foetus, - foetusComparator, - parity, - parityComparator, - maternalRisks, - maternalRisksPrecision, - risksRelatedToObstetricHistory, - risksRelatedToObstetricHistoryPrecision, - risksOrComplicationsOfPregnancy, - risksOrComplicationsOfPregnancyPrecision, - corticotherapie, - prenatalDiagnosis, - ultrasoundMonitoring, - encounterService, - occurrence, - occurrenceComparator, - startOccurrence: [null, null], - encounterStatus, - isInclusive, - title - }) - } - return ( - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - /> - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={encounterStatus} - onChange={(e, value) => setEncounterStatus(value)} - renderInput={(params) => } - /> - - - - - - - { - setPregnancyStartDate(start) - setPregnancyEndDate(end) - }} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={pregnancyMode} - onChange={(e, value) => setPregnancyMode(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - { - setFoetus(newFoetus) - setFoetusComparator(newComparator) - }} - /> - - - - { - setParity(newParity) - setParityComparator(newComparator) - }} - /> - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={maternalRisks} - onChange={(e, value) => setMaternalRisks(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={risksRelatedToObstetricHistory} - onChange={(e, value) => setRisksRelatedToObstetricHistory(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={ultrasoundMonitoring} - onChange={(e, value) => setUltrasoundMonitoring(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={corticotherapie} - onChange={(e, value) => setCorticotherapie(value)} - renderInput={(params) => ( - - )} - style={{ marginBottom: '1em' }} - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={risksOrComplicationsOfPregnancy} - onChange={(e, value) => setRisksOrComplicationsOfPregnancy(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={prenatalDiagnosis} - onChange={(e, value) => setPrenatalDiagnosis(value)} - renderInput={(params) => } - /> - - - - - ) -} - -export default PregnantForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/PregnantForm/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/PregnantForm/styles.ts deleted file mode 100644 index c4ea9e3ea..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/PregnantForm/styles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - inputItem: { - margin: '1em' - }, - marginBottom: { - marginBottom: '1em' - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/RequestForm/RequestForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/RequestForm/RequestForm.tsx index bb9a25cef..cd3787c11 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/RequestForm/RequestForm.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/RequestForm/RequestForm.tsx @@ -28,7 +28,7 @@ const RequestForm: React.FC = ({ parentId, goBack return setError(true) } - dispatch(addRequestToCohortCreation({ parentId, selectedRequestId })) + dispatch(addRequestToCohortCreation({ parentId: parentId ?? null, selectedRequestId })) } return ( diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/BiologyForm.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/BiologyForm.ts new file mode 100644 index 000000000..57ca4c0ff --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/BiologyForm.ts @@ -0,0 +1,165 @@ +import { ObservationParamsKeys, Comparators, CriteriaType, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + NumberAndComparatorDataType, + WithEncounterDateDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { LabelObject } from 'types/searchCriterias' +import { SourceType } from 'types/scope' +import { getConfig } from 'config' +import { BiologyStatus } from 'types' + +export type ObservationDataType = CommonCriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterDateDataType & + WithEncounterStatusDataType & { + type: CriteriaType.OBSERVATION + code: LabelObject[] | null + searchByValue: NumberAndComparatorDataType | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'Biologie', + initialData: { + id: undefined, + type: CriteriaType.OBSERVATION, + title: 'Critères de biologie', + isInclusive: true, + occurrence: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + encounterService: null, + startOccurrence: null, + endOccurrence: null, + encounterStartDate: null, + encounterEndDate: null, + encounterStatus: [], + code: null, + searchByValue: null + }, + warningAlert: [ + "Les mesures de biologie sont pour l'instant restreintes aux 3870 codes ANABIO correspondants aux analyses les plus utilisées au niveau national et à l'AP-HP. De plus, les résultats concernent uniquement les analyses quantitatives enregistrées sur GLIMS, qui ont été validées et mises à jour depuis mars 2020." + ], + errorMessages: { + INCOHERENT_VALUE_ERROR: 'La valeur minimale ne peut pas être supérieure à la valeur maximale.', + INVALID_VALUE_ERROR: 'Veuillez entrer un nombre valide.', + MISSING_VALUE_ERROR: 'Veuillez entrer 2 valeurs avec ce comparateur.', + ADVANCED_INPUTS_ERROR: 'Erreur dans les options avancées.', + NO_ERROR: '' + }, + buildInfo: { + criteriaType: CriteriaType.OBSERVATION, + resourceType: ResourceType.OBSERVATION, + defaultFilter: `subject.active=true&${ObservationParamsKeys.VALIDATED_STATUS}=${BiologyStatus.VALIDATED}` + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'code', + type: 'codeSearch', + label: 'Codes de biologie', + checkIsLeaf: true, + valueSetIds: [ + getConfig().features.observation.valueSets.biologyHierarchyAnabio.url, + getConfig().features.observation.valueSets.biologyHierarchyLoinc.url + ], + noOptionsText: 'Veuillez entrer un code de biologie', + buildInfo: { + fhirKey: ObservationParamsKeys.ANABIO_LOINC, + buildMethodExtraArgs: [ + { type: 'string', value: getConfig().features.observation.valueSets.biologyHierarchyAnabio.url } + ] + } + }, + { + valueKey: 'searchByValue', + type: 'numberAndComparator', + label: 'Recherche par valeur', + allowBetween: true, + withHierarchyInfo: false, + disableCondition: (data) => { + const typedData = data as ObservationDataType + return typedData.code?.length !== 1 || !typedData.code[0].isLeaf + }, + buildInfo: { + fhirKey: ObservationParamsKeys.VALUE, + buildMethodExtraArgs: [{ type: 'string', value: 'le0,ge0' }], + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Valeur' }] + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée', + buildInfo: { + fhirKey: ObservationParamsKeys.ENCOUNTER_STATUS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite associée' }] + } + } + ] + }, + { + title: 'Options avancées', + defaulCollapsed: true, + items: [ + { + valueKey: 'encounterService', + label: 'Unité exécutrice', + type: 'executiveUnit', + sourceType: SourceType.BIOLOGY, + buildInfo: { + fhirKey: ObservationParamsKeys.EXECUTIVE_UNITS + } + }, + { + valueKey: 'encounterStartDate', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + label: 'Début de prise en charge', + labelAltStyle: true, + extraLabel: () => 'Prise en charge', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-start', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de début de prise en charge' }] + } + }, + { + valueKey: 'encounterEndDate', + type: 'calendarRange', + label: 'Fin de prise en charge', + labelAltStyle: true, + errorType: 'ADVANCED_INPUTS_ERROR', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-end', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de fin de prise en charge' }] + } + }, + { + valueKey: 'startOccurrence', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + extraLabel: () => "Date de l'examen", + buildInfo: { + fhirKey: ObservationParamsKeys.DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Date de l'examen" }] + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/CCAMForm.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/CCAMForm.ts new file mode 100644 index 000000000..53e1e3b3d --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/CCAMForm.ts @@ -0,0 +1,168 @@ +import { ProcedureParamsKeys, Comparators, CriteriaType, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + WithEncounterDateDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { LabelObject } from 'types/searchCriterias' +import { SourceType } from 'types/scope' +import { getConfig } from 'config' + +export type CcamDataType = CommonCriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterDateDataType & + WithEncounterStatusDataType & { + type: CriteriaType.PROCEDURE + code: LabelObject[] | null + source: string | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'Actes CCAM', + initialData: { + type: CriteriaType.PROCEDURE, + title: "Critères d'actes CCAM", + isInclusive: true, + occurrence: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + encounterService: null, + startOccurrence: null, + encounterStartDate: null, + encounterEndDate: null, + encounterStatus: [], + code: null, + source: 'AREM' + }, + errorMessages: { + INCOHERENT_VALUE_ERROR: 'La valeur minimale ne peut pas être supérieure à la valeur maximale.', + INVALID_VALUE_ERROR: 'Veuillez entrer un nombre valide.', + MISSING_VALUE_ERROR: 'Veuillez entrer 2 valeurs avec ce comparateur.', + ADVANCED_INPUTS_ERROR: 'Erreur dans les options avancées.', + NO_ERROR: '' + }, + buildInfo: { + criteriaType: CriteriaType.PROCEDURE, + resourceType: ResourceType.PROCEDURE, + defaultFilter: 'subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'source', + type: 'radioChoice', + label: 'Source', + choices: [ + { id: 'AREM', label: 'AREM' }, + { id: 'ORBIS', label: 'ORBIS' } + ], + buildInfo: { + fhirKey: ProcedureParamsKeys.SOURCE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Source: ' }] + } + }, + { + type: 'info', + content: 'Les données AREM sont disponibles uniquement pour la période du 07/12/2009 au 31/12/2022.', + contentType: 'warning' + }, + { + type: 'info', + content: + 'Seuls les diagnostics rattachés à une visite Orbis (avec un Dossier Administratif - NDA) sont actuellement disponibles.', + contentType: 'warning' + }, + { + type: 'info', + content: + "Les données PMSI d'ORBIS sont codées au quotidien par les médecins. Les données PMSI AREM sont validées, remontées aux tutelles et disponibles dans le SNDS.", + contentType: 'info' + }, + { + valueKey: 'code', + type: 'codeSearch', + label: "Codes d'actes CCAM", + valueSetIds: [getConfig().features.procedure.valueSets.procedureHierarchy.url], + noOptionsText: 'Veuillez entrer un code ou un acte CCAM', + buildInfo: { + fhirKey: ProcedureParamsKeys.CODE, + buildMethodExtraArgs: [ + { type: 'string', value: getConfig().features.procedure.valueSets.procedureHierarchy.url } + ] + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée', + buildInfo: { + fhirKey: ProcedureParamsKeys.ENCOUNTER_STATUS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite associée' }] + } + } + ] + }, + { + title: 'Options avancées', + defaulCollapsed: true, + items: [ + { + valueKey: 'encounterService', + label: 'Unité exécutrice', + type: 'executiveUnit', + sourceType: SourceType.CCAM, + buildInfo: { + fhirKey: ProcedureParamsKeys.EXECUTIVE_UNITS + } + }, + { + valueKey: 'encounterStartDate', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + label: 'Début de prise en charge', + labelAltStyle: true, + extraLabel: () => 'Prise en charge', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-start', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de début de prise en charge' }] + } + }, + { + valueKey: 'encounterEndDate', + type: 'calendarRange', + label: 'Fin de prise en charge', + labelAltStyle: true, + errorType: 'ADVANCED_INPUTS_ERROR', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-end', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de fin de prise en charge' }] + } + }, + { + valueKey: 'startOccurrence', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + extraLabel: () => 'Date de classement en GHM', + buildInfo: { + fhirKey: ProcedureParamsKeys.DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de classement en GHM' }] + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/Cim10Form.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/Cim10Form.ts new file mode 100644 index 000000000..edf8f4b25 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/Cim10Form.ts @@ -0,0 +1,180 @@ +import { Comparators, ConditionParamsKeys, CriteriaType, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + WithEncounterDateDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { LabelObject } from 'types/searchCriterias' +import { SourceType } from 'types/scope' +import { getConfig } from 'config' + +export type Cim10DataType = CommonCriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterDateDataType & + WithEncounterStatusDataType & { + type: CriteriaType.CONDITION + code: LabelObject[] | null + source: string | null + diagnosticType: LabelObject[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'Diagnostic', + initialData: { + type: CriteriaType.CONDITION, + title: 'Critère de diagnostic', + isInclusive: true, + occurrence: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + encounterService: null, + startOccurrence: null, + encounterStartDate: null, + encounterEndDate: null, + encounterStatus: [], + code: null, + source: 'AREM', + diagnosticType: null + }, + errorMessages: { + INCOHERENT_VALUE_ERROR: 'La valeur minimale ne peut pas être supérieure à la valeur maximale.', + INVALID_VALUE_ERROR: 'Veuillez entrer un nombre valide.', + MISSING_VALUE_ERROR: 'Veuillez entrer 2 valeurs avec ce comparateur.', + ADVANCED_INPUTS_ERROR: 'Erreur dans les options avancées.', + NO_ERROR: '' + }, + buildInfo: { + criteriaType: CriteriaType.CONDITION, + resourceType: ResourceType.CONDITION, + defaultFilter: 'subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'source', + type: 'radioChoice', + label: 'Source', + choices: [ + { id: 'AREM', label: 'AREM' }, + { id: 'ORBIS', label: 'ORBIS' } + ], + buildInfo: { + fhirKey: ConditionParamsKeys.SOURCE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Source: ' }] + } + }, + { + type: 'info', + content: 'Les données AREM sont disponibles uniquement pour la période du 07/12/2009 au 31/12/2022.', + contentType: 'warning' + }, + { + type: 'info', + content: + 'Seuls les diagnostics rattachés à une visite Orbis (avec un Dossier Administratif - NDA) sont actuellement disponibles.', + contentType: 'warning' + }, + { + type: 'info', + content: + "Les données PMSI d'ORBIS sont codées au quotidien par les médecins. Les données PMSI AREM sont validées, remontées aux tutelles et disponibles dans le SNDS.", + contentType: 'info' + }, + { + valueKey: 'code', + type: 'codeSearch', + valueSetIds: [getConfig().features.condition.valueSets.conditionHierarchy.url], + noOptionsText: 'Veuillez entrer un code ou un diagnostic CIM10', + label: 'Code CIM10', + buildInfo: { + fhirKey: ConditionParamsKeys.CODE, + buildMethodExtraArgs: [ + { type: 'string', value: getConfig().features.condition.valueSets.conditionHierarchy.url } + ] + } + }, + { + valueKey: 'diagnosticType', + type: 'autocomplete', + label: 'Type de diagnostic', + valueSetId: getConfig().features.condition.valueSets.conditionStatus.url, + noOptionsText: 'Veuillez entrer un type de diagnostic', + buildInfo: { + fhirKey: ConditionParamsKeys.DIAGNOSTIC_TYPES + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée', + buildInfo: { + fhirKey: ConditionParamsKeys.ENCOUNTER_STATUS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite associée' }] + } + } + ] + }, + { + title: 'Options avancées', + defaulCollapsed: true, + items: [ + { + valueKey: 'encounterService', + label: 'Unité exécutrice', + type: 'executiveUnit', + sourceType: SourceType.CIM10, + buildInfo: { + fhirKey: ConditionParamsKeys.EXECUTIVE_UNITS + } + }, + { + valueKey: 'encounterStartDate', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + label: 'Début de prise en charge', + labelAltStyle: true, + extraLabel: () => 'Prise en charge', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-start', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de début de prise en charge' }] + } + }, + { + valueKey: 'encounterEndDate', + type: 'calendarRange', + label: 'Fin de prise en charge', + labelAltStyle: true, + errorType: 'ADVANCED_INPUTS_ERROR', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-end', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de fin de prise en charge' }] + } + }, + { + valueKey: 'startOccurrence', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + extraLabel: () => 'Date du diagnostic CIM10', + buildInfo: { + fhirKey: ConditionParamsKeys.DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date du diagnostic CIM10' }] + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DemographicForm.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DemographicForm.ts new file mode 100644 index 000000000..af95d15c8 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DemographicForm.ts @@ -0,0 +1,151 @@ +import { Comparators, CriteriaType, PatientsParamsKeys, ResourceType } from 'types/requestCriterias' +import { CommonCriteriaData, CriteriaForm, NewDurationRangeType } from '../CriteriaForm/types' +import { LabelObject, VitalStatusOptionsLabel } from 'types/searchCriterias' +import { getConfig } from 'config' + +export type DemographicDataType = CommonCriteriaData & { + type: CriteriaType.PATIENT + genders: LabelObject[] | null + vitalStatus: LabelObject[] | null + age: NewDurationRangeType | null + birthdates: NewDurationRangeType | null + deathDates: NewDurationRangeType | null +} + +export const form: () => CriteriaForm = () => ({ + label: 'Démographie patient', + initialData: { + type: CriteriaType.PATIENT, + title: 'Critère démographique', + isInclusive: true, + occurrence: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + encounterService: null, + startOccurrence: null, + encounterStartDate: null, + encounterEndDate: null, + encounterStatus: [], + genders: null, + vitalStatus: null, + age: null, + birthdates: null, + deathDates: null + }, + errorMessages: { + INCOHERENT_VALUE_ERROR: 'La valeur minimale ne peut pas être supérieure à la valeur maximale.', + INVALID_VALUE_ERROR: 'Veuillez entrer un nombre valide.', + MISSING_VALUE_ERROR: 'Veuillez entrer 2 valeurs avec ce comparateur.', + ADVANCED_INPUTS_ERROR: 'Erreur dans les options avancées.', + NO_ERROR: '' + }, + buildInfo: { + criteriaType: CriteriaType.PATIENT, + resourceType: ResourceType.PATIENT, + defaultFilter: 'active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'genders', + type: 'autocomplete', + label: 'Genre', + valueSetId: getConfig().core.valueSets.demographicGender.url, + noOptionsText: 'Veuillez entrer un genre', + buildInfo: { + fhirKey: PatientsParamsKeys.GENDERS + } + }, + { + valueKey: 'vitalStatus', + type: 'autocomplete', + label: 'Statut vital', + valueSetId: 'vitalStatus', + valueSetData: [ + { + id: 'true', + label: 'Patients Vivants' + }, + { + id: 'false', + label: 'Patient Décédés' + } + ], + noOptionsText: 'Veuillez entrer un statut vital', + buildInfo: { + fhirKey: PatientsParamsKeys.VITAL_STATUS + } + }, + { + valueKey: 'birthdates', + type: 'calendarRange', + label: 'Date de naissance', + errorType: 'INCOHERENT_VALUE_ERROR', + displayCondition: (data, context) => { + return !context.deidentified + }, + disableCondition: (data) => { + const typedData = data as DemographicDataType + return typedData.age !== null && (typedData.age.start !== null || typedData.age.end !== null) + }, + buildInfo: { + fhirKey: PatientsParamsKeys.BIRTHDATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Naissance' }] + } + }, + { + valueKey: 'age', + type: 'durationRange', + extraLabel: (data) => { + const typedData = data as DemographicDataType + const vitalStatus = typedData.vitalStatus + return vitalStatus && + vitalStatus.length === 1 && + vitalStatus.find((status: LabelObject) => status.id === 'false') + ? VitalStatusOptionsLabel.deceasedAge + : VitalStatusOptionsLabel.age + }, + disableCondition: (data) => { + const typedData = data as DemographicDataType + console.log( + 'disabled', + typedData.birthdates !== null && + (typedData.birthdates.start !== null || typedData.birthdates.end !== null) + ) + return ( + typedData.birthdates !== null && + (typedData.birthdates.start !== null || typedData.birthdates.end !== null) + ) + }, + buildInfo: { + // TODO there is orignially a condition where + // criterion.birthdates[0] === null && criterion.birthdates[1] === null + // must be true for the filter to be applied + // check if this is still necessary (cause the durationRange / calendarRange set the value to null when inner fields are null now) + fhirKey: { main: PatientsParamsKeys.DATE_IDENTIFIED, deid: PatientsParamsKeys.DATE_DEIDENTIFIED }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Âge' }] + } + }, + { + valueKey: 'deathDates', + type: 'calendarRange', + label: 'Date de décès', + errorType: 'INCOHERENT_VALUE_ERROR', + displayCondition: (data, context) => { + const typedData = data as DemographicDataType + const vitalStatus = typedData.vitalStatus + return ( + !context.deidentified && + (vitalStatus === null || + vitalStatus.length === 0 || + (vitalStatus.length === 1 && !!vitalStatus.find((status: LabelObject) => status.id === 'false'))) + ) + }, + buildInfo: { + fhirKey: PatientsParamsKeys.DEATHDATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Décès' }] + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DocumentsForm.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DocumentsForm.ts new file mode 100644 index 000000000..843481ecb --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DocumentsForm.ts @@ -0,0 +1,197 @@ +import { Comparators, CriteriaType, DocumentsParamsKeys, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + WithEncounterDateDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { DocumentStatuses, FilterByDocumentStatus, LabelObject, SearchByTypes } from 'types/searchCriterias' +import { getConfig } from 'config' +import docTypes from 'assets/docTypes.json' +import { SourceType } from 'types/scope' + +export type DocumentDataType = CommonCriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterDateDataType & + WithEncounterStatusDataType & { + type: CriteriaType.DOCUMENTS + search: string + searchBy: string // SearchByTypes.TEXT | SearchByTypes.DESCRIPTION + docType: LabelObject[] | null + docStatuses: LabelObject[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'Documents médicaux', + initialData: { + type: CriteriaType.DOCUMENTS, + title: 'Critère de document', + isInclusive: true, + occurrence: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + encounterService: null, + startOccurrence: null, + encounterStartDate: null, + encounterEndDate: null, + encounterStatus: [], + search: '', + searchBy: 'TEXT', // Default to SearchByTypes.TEXT + docType: null, + docStatuses: null + }, + errorMessages: { + INCOHERENT_VALUE_ERROR: 'La valeur minimale ne peut pas être supérieure à la valeur maximale.', + INVALID_VALUE_ERROR: 'Veuillez entrer un nombre valide.', + MISSING_VALUE_ERROR: 'Veuillez entrer 2 valeurs avec ce comparateur.', + ADVANCED_INPUTS_ERROR: 'Erreur dans les options avancées.', + NO_ERROR: '' + }, + buildInfo: { + criteriaType: CriteriaType.DOCUMENTS, + resourceType: ResourceType.DOCUMENTS, + defaultFilter: 'type:not=doc-impor&contenttype=text/plain&subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: 'Occurrence', + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'searchBy', + type: 'radioChoice', + label: 'Rechercher dans :', + choices: [ + { id: 'TEXT', label: 'Corps du document' }, + { id: 'DESCRIPTION', label: 'Titre du document' } + ] + }, + { + valueKey: 'search', + type: 'textWithCheck', + label: 'Rechercher dans les documents', + placeholder: 'Rechercher dans les documents', + errorType: 'error', + buildInfo: { + fhirKey: { + main: SearchByTypes.TEXT, + alt: SearchByTypes.DESCRIPTION, + value1: { type: 'string', value: SearchByTypes.TEXT }, + value2: { type: 'reference', value: 'searchBy' } + }, + chipDisplayMethod: 'getSearchDocumentLabel', + chipDisplayMethodExtraArgs: [{ type: 'reference', value: 'searchBy' }] + } + }, + { + valueKey: 'docType', + type: 'autocomplete', + label: 'Types de documents', + valueSetId: 'docTypesValueSetId', + valueSetData: docTypes.docTypes.map((docType) => ({ + id: docType.code, + label: docType.label, + type: docType.type + })), + noOptionsText: 'Veuillez entrer un type de document', + groupBy: 'type', + buildInfo: { + fhirKey: DocumentsParamsKeys.DOC_TYPES, + chipDisplayMethod: 'getDocumentTypesLabel' + } + }, + { + valueKey: 'docStatuses', + type: 'autocomplete', + label: 'Statut de documents', + valueSetId: 'docStatusesValueSetId', + valueSetData: [ + { + id: DocumentStatuses.FINAL, + label: FilterByDocumentStatus.VALIDATED + }, + { + id: DocumentStatuses.PRELIMINARY, + label: FilterByDocumentStatus.NOT_VALIDATED + } + ], + noOptionsText: 'Veuillez entrer un statut de document', + buildInfo: { + fhirKey: DocumentsParamsKeys.DOC_STATUSES, + buildMethodExtraArgs: [ + { type: 'string', value: getConfig().core.codeSystems.docStatus }, + { type: 'boolean', value: true } + ], + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de documents : ' }] + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée', + buildInfo: { + fhirKey: DocumentsParamsKeys.ENCOUNTER_STATUS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite associée' }] + } + } + ] + }, + { + title: 'Options avancées', + defaulCollapsed: true, + items: [ + { + valueKey: 'encounterService', + label: 'Unité exécutrice', + type: 'executiveUnit', + sourceType: SourceType.DOCUMENT, + buildInfo: { + fhirKey: DocumentsParamsKeys.EXECUTIVE_UNITS + } + }, + { + valueKey: 'encounterStartDate', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + label: 'Début de prise en charge', + labelAltStyle: true, + extraLabel: () => 'Prise en charge', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-start', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de début de prise en charge' }] + } + }, + { + valueKey: 'encounterEndDate', + type: 'calendarRange', + label: 'Fin de prise en charge', + labelAltStyle: true, + errorType: 'ADVANCED_INPUTS_ERROR', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-end', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de fin de prise en charge' }] + } + }, + { + valueKey: 'startOccurrence', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + extraLabel: () => 'Date de création du document', + buildInfo: { + fhirKey: DocumentsParamsKeys.DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de création du document' }] + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/EncounterForm.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/EncounterForm.ts new file mode 100644 index 000000000..f1f1742ed --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/EncounterForm.ts @@ -0,0 +1,263 @@ +import { EncounterParamsKeys, Comparators, CriteriaType, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + NewDurationRangeType, + WithEncounterDateDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { LabelObject } from 'types/searchCriterias' +import { SourceType } from 'types/scope' +import { getConfig } from 'config' + +export type EncounterDataType = CommonCriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterDateDataType & + WithEncounterStatusDataType & { + type: CriteriaType.ENCOUNTER + age: NewDurationRangeType | null + duration: NewDurationRangeType | null + admissionMode: LabelObject[] | null + entryMode: LabelObject[] | null + exitMode: LabelObject[] | null + priseEnChargeType: LabelObject[] | null + typeDeSejour: LabelObject[] | null + reason: LabelObject[] | null + destination: LabelObject[] | null + provenance: LabelObject[] | null + admission: LabelObject[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'Prise en charge', + initialData: { + type: CriteriaType.ENCOUNTER, + title: 'Critère de prise en charge', + isInclusive: true, + occurrence: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + encounterService: null, + startOccurrence: null, + encounterStartDate: null, + encounterEndDate: null, + encounterStatus: [], + age: null, + duration: null, + admissionMode: null, + entryMode: null, + exitMode: null, + priseEnChargeType: null, + typeDeSejour: null, + reason: null, + destination: null, + provenance: null, + admission: null + }, + errorMessages: { + INCOHERENT_VALUE_ERROR: 'La valeur minimale ne peut pas être supérieure à la valeur maximale.', + INVALID_VALUE_ERROR: 'Veuillez entrer un nombre valide.', + MISSING_VALUE_ERROR: 'Veuillez entrer 2 valeurs avec ce comparateur.', + ADVANCED_INPUTS_ERROR: 'Erreur dans les options avancées.', + NO_ERROR: '' + }, + buildInfo: { + criteriaType: CriteriaType.ENCOUNTER, + resourceType: ResourceType.ENCOUNTER, + defaultFilter: 'subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: 'Occurrence', + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'encounterService', + type: 'executiveUnit', + label: 'Service de rencontre', + sourceType: SourceType.DOCUMENT, // TODO add a new source type for encounter + buildInfo: { + fhirKey: EncounterParamsKeys.SERVICE_PROVIDER + } + }, + { + valueKey: 'age', + type: 'durationRange', + extraLabel: () => 'Âge au début de la prise en charge', + info: "La valeur par défaut sera prise en compte si le sélecteur d'âge n'a pas été modifié.", + buildInfo: { + fhirKey: { + main: EncounterParamsKeys.MIN_BIRTHDATE_DAY, + deid: EncounterParamsKeys.MIN_BIRTHDATE_MONTH + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Âge : ' }] + } + }, + { + valueKey: 'duration', + type: 'durationRange', + extraLabel: () => 'Durée de la prise en charge', + buildInfo: { + fhirKey: EncounterParamsKeys.DURATION, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Prise en charge : ' }] + } + }, + { + valueKey: 'encounterStartDate', + type: 'calendarRange', + label: 'Début de prise en charge', + extraLabel: () => 'Durée de prise en charge', + labelAltStyle: true, + withOptionIncludeNull: true, + errorType: 'INCOHERENT_VALUE_ERROR', + buildInfo: { + fhirKey: EncounterParamsKeys.START_DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de début de prise en charge' }] + } + }, + { + valueKey: 'encounterEndDate', + type: 'calendarRange', + label: 'Fin de prise en charge', + labelAltStyle: true, + withOptionIncludeNull: true, + errorType: 'INCOHERENT_VALUE_ERROR', + buildInfo: { + fhirKey: EncounterParamsKeys.END_DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de fin de prise en charge' }] + } + } + ] + }, + { + title: 'Général', + defaulCollapsed: true, + items: [ + { + valueKey: 'priseEnChargeType', + type: 'autocomplete', + label: 'Type de prise en charge', + valueSetId: getConfig().core.valueSets.encounterVisitType.url, + noOptionsText: 'Veuillez entrer un type de prise en charge', + buildInfo: { + fhirKey: EncounterParamsKeys.PRISENCHARGETYPE + } + }, + { + valueKey: 'typeDeSejour', + type: 'autocomplete', + label: 'Type séjour', + valueSetId: getConfig().core.valueSets.encounterSejourType.url, + noOptionsText: 'Veuillez entrer un type de séjour', + buildInfo: { + fhirKey: EncounterParamsKeys.TYPEDESEJOUR + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée', + buildInfo: { + fhirKey: EncounterParamsKeys.STATUS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite' }] + } + } + ] + }, + { + title: 'Admission', + defaulCollapsed: true, + items: [ + { + valueKey: 'admissionMode', + type: 'autocomplete', + label: 'Motif Admission', + valueSetId: getConfig().core.valueSets.encounterAdmissionMode.url, + noOptionsText: "Veuillez entrer un motif d'admission", + buildInfo: { + fhirKey: EncounterParamsKeys.ADMISSIONMODE + } + }, + { + valueKey: 'admission', + type: 'autocomplete', + label: 'Type Admission', + valueSetId: getConfig().core.valueSets.encounterAdmission.url, + noOptionsText: "Veuillez entrer un type d'admission", + buildInfo: { + fhirKey: EncounterParamsKeys.ADMISSION + } + } + ] + }, + { + title: 'Entrée / Sortie', + defaulCollapsed: true, + items: [ + { + valueKey: 'entryMode', + type: 'autocomplete', + label: 'Mode entrée', + valueSetId: getConfig().core.valueSets.encounterEntryMode.url, + noOptionsText: "Veuillez entrer un mode d'entrée", + buildInfo: { + fhirKey: EncounterParamsKeys.ENTRYMODE + } + }, + { + valueKey: 'exitMode', + type: 'autocomplete', + label: 'Mode sortie', + valueSetId: getConfig().core.valueSets.encounterExitMode.url, + noOptionsText: 'Veuillez entrer un mode de sortie', + buildInfo: { + fhirKey: EncounterParamsKeys.EXITMODE + } + }, + { + valueKey: 'reason', + type: 'autocomplete', + label: 'Type sortie', + valueSetId: getConfig().core.valueSets.encounterExitType.url, + noOptionsText: 'Veuillez entrer un type de sortie', + buildInfo: { + fhirKey: EncounterParamsKeys.REASON + } + } + ] + }, + { + title: 'Destination / Provenance', + defaulCollapsed: true, + items: [ + { + valueKey: 'destination', + type: 'autocomplete', + label: 'Destination', + valueSetId: getConfig().core.valueSets.encounterDestination.url, + noOptionsText: 'Veuillez entrer une destination', + buildInfo: { + fhirKey: EncounterParamsKeys.DESTINATION + } + }, + { + valueKey: 'provenance', + type: 'autocomplete', + label: 'Provenance', + valueSetId: getConfig().core.valueSets.encounterProvenance.url, + noOptionsText: 'Veuillez entrer une provenance', + buildInfo: { + fhirKey: EncounterParamsKeys.PROVENANCE + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/GHMForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/GHMForm.tsx new file mode 100644 index 000000000..7be2197c9 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/GHMForm.tsx @@ -0,0 +1,146 @@ +import React from 'react' +import { ClaimParamsKeys, Comparators, CriteriaType, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + WithEncounterDateDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { LabelObject } from 'types/searchCriterias' +import { Link } from '@mui/material' +import { SourceType } from 'types/scope' +import { getConfig } from 'config' + +export type GhmDataType = CommonCriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterDateDataType & + WithEncounterStatusDataType & { + type: CriteriaType.CLAIM + code: LabelObject[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'GHM', + initialData: { + title: 'Critères GHM', + type: CriteriaType.CLAIM, + isInclusive: true, + occurrence: { + value: 1, + comparator: Comparators.GREATER_OR_EQUAL + }, + encounterService: null, + encounterStatus: [], + startOccurrence: null, + encounterStartDate: null, + encounterEndDate: null, + code: [] + }, + warningAlert: [ +
+ Données actuellement disponibles : PMSI ORBIS. Pour plus d'informations sur les prochaines intégrations de + données, veuillez vous référer au tableau trimestriel de disponibilité des données disponible{' '} + + ici + +
+ ], + buildInfo: { + criteriaType: CriteriaType.CLAIM, + resourceType: ResourceType.CLAIM, + defaultFilter: 'patient.active=true' + }, + errorMessages: {}, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'code', + type: 'codeSearch', + valueSetIds: [getConfig().features.claim.valueSets.claimHierarchy.url], + noOptionsText: 'Aucun GHM trouvé', + label: 'Code GHM', + buildInfo: { + fhirKey: ClaimParamsKeys.CODE, + buildMethodExtraArgs: [{ type: 'string', value: getConfig().features.claim.valueSets.claimHierarchy.url }] + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Aucun statut trouvé', + buildInfo: { + fhirKey: ClaimParamsKeys.ENCOUNTER_STATUS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite associée' }] + } + } + ] + }, + { + title: 'Options avancées', + defaulCollapsed: true, + items: [ + { + valueKey: 'encounterService', + label: 'Unité exécutrice', + type: 'executiveUnit', + sourceType: SourceType.GHM, + buildInfo: { + fhirKey: ClaimParamsKeys.EXECUTIVE_UNITS + } + }, + { + valueKey: 'encounterStartDate', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + label: 'Début de prise en charge', + labelAltStyle: true, + extraLabel: () => 'Prise en charge', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-start', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de début de prise en charge' }] + } + }, + { + valueKey: 'encounterEndDate', + type: 'calendarRange', + label: 'Fin de prise en charge', + labelAltStyle: true, + errorType: 'ADVANCED_INPUTS_ERROR', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-end', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de fin de prise en charge' }] + } + }, + { + valueKey: 'startOccurrence', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + extraLabel: () => "Date d'acte CCAM", + buildInfo: { + fhirKey: ClaimParamsKeys.DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Date de l'acte CCAM" }] + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/HospitForm.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/HospitForm.ts new file mode 100644 index 000000000..43d28d7cf --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/HospitForm.ts @@ -0,0 +1,769 @@ +import { Comparators, CriteriaType, QuestionnaireResponseParamsKeys, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + NewDurationRangeType, + NumberAndComparatorDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { FormNames, LabelObject } from 'types/searchCriterias' +import { SourceType } from 'types/scope' +import { getConfig } from 'config' + +export type HospitDataType = CommonCriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterStatusDataType & { + type: CriteriaType.HOSPIT + hospitReason: string + inUteroTransfer: LabelObject[] | null + pregnancyMonitoring: LabelObject[] | null + vme: LabelObject[] | null + maturationCorticotherapie: LabelObject[] | null + chirurgicalGesture: LabelObject[] | null + childbirth: LabelObject[] | null + hospitalChildBirthPlace: LabelObject[] | null + otherHospitalChildBirthPlace: LabelObject[] | null + homeChildBirthPlace: LabelObject[] | null + childbirthMode: LabelObject[] | null + maturationReason: LabelObject[] | null + maturationModality: LabelObject[] | null + imgIndication: LabelObject[] | null + laborOrCesareanEntry: LabelObject[] | null + pathologyDuringLabor: LabelObject[] | null + obstetricalGestureDuringLabor: LabelObject[] | null + analgesieType: LabelObject[] | null + birthDeliveryDate: NewDurationRangeType | null + birthDeliveryWeeks: NumberAndComparatorDataType + birthDeliveryDays: NumberAndComparatorDataType + birthDeliveryWay: LabelObject[] | null + instrumentType: LabelObject[] | null + cSectionModality: LabelObject[] | null + presentationAtDelivery: LabelObject[] | null + birthMensurationsGrams: NumberAndComparatorDataType + birthMensurationsPercentil: NumberAndComparatorDataType + apgar1: NumberAndComparatorDataType + apgar3: NumberAndComparatorDataType + apgar5: NumberAndComparatorDataType + apgar10: NumberAndComparatorDataType + arterialPhCord: NumberAndComparatorDataType + arterialCordLactates: NumberAndComparatorDataType + birthStatus: LabelObject[] | null + postpartumHemorrhage: LabelObject[] | null + conditionPerineum: LabelObject[] | null + exitPlaceType: LabelObject[] | null + feedingType: LabelObject[] | null + complication: LabelObject[] | null + exitFeedingMode: LabelObject[] | null + exitDiagnostic: LabelObject[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: "Critère de Fiche d'hopsitalisation", + initialData: { + title: "Critère de Fiche d'hospitalisation", + type: CriteriaType.HOSPIT, + isInclusive: true, + occurrence: { + value: 1, + comparator: Comparators.GREATER_OR_EQUAL + }, + encounterService: null, + encounterStatus: [], + startOccurrence: null, + hospitReason: '', + inUteroTransfer: null, + pregnancyMonitoring: null, + vme: null, + maturationCorticotherapie: null, + chirurgicalGesture: null, + childbirth: null, + hospitalChildBirthPlace: null, + otherHospitalChildBirthPlace: null, + homeChildBirthPlace: null, + childbirthMode: null, + maturationReason: null, + maturationModality: null, + imgIndication: null, + laborOrCesareanEntry: null, + pathologyDuringLabor: null, + obstetricalGestureDuringLabor: null, + analgesieType: null, + birthDeliveryDate: null, + birthDeliveryWeeks: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + birthDeliveryDays: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + birthDeliveryWay: null, + instrumentType: null, + cSectionModality: null, + presentationAtDelivery: null, + birthMensurationsGrams: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + birthMensurationsPercentil: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + apgar1: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + apgar3: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + apgar5: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + apgar10: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + arterialPhCord: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + arterialCordLactates: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + birthStatus: null, + postpartumHemorrhage: null, + conditionPerineum: null, + exitPlaceType: null, + feedingType: null, + complication: null, + exitFeedingMode: null, + exitDiagnostic: null + }, + errorMessages: {}, + buildInfo: { + criteriaType: CriteriaType.HOSPIT, + resourceType: ResourceType.QUESTIONNAIRE_RESPONSE, + defaultFilter: `subject.active=true&questionnaire.name=${FormNames.HOSPIT}&status=in-progress,completed`, + subType: FormNames.HOSPIT + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'encounterService', + type: 'executiveUnit', + label: 'Unité exécutrice', + sourceType: SourceType.MATERNITY, + buildInfo: { + fhirKey: QuestionnaireResponseParamsKeys.EXECUTIVE_UNITS + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée', + buildInfo: { + fhirKey: { + id: QuestionnaireResponseParamsKeys.ENCOUNTER_STATUS, + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite associée' }] + } + } + ] + }, + { + title: 'ADMISSION', + defaulCollapsed: true, + items: [ + { + valueKey: 'hospitReason', + type: 'textWithCheck', + placeholder: "Motif(s) d'hospitalisation", + errorType: 'SEARCHINPUT_ERROR', + buildInfo: { + fhirKey: { + id: 'F_MATER_004051', + type: 'valueString' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Motif(s) d'hospitalisation : " }] + } + }, + { + valueKey: 'inUteroTransfer', + type: 'autocomplete', + label: 'Transfert in utero', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_007001', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Transfert in utero :' }] + } + }, + { + valueKey: 'pregnancyMonitoring', + type: 'autocomplete', + label: 'Grossesse peu ou pas suivie', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_004062', + type: 'valueBoolean' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Grossesse peu ou pas suivie :' }] + } + }, + { + valueKey: 'vme', + type: 'autocomplete', + label: 'VME', + noOptionsText: 'Veuillez entrer des valeurs de VME', + valueSetId: getConfig().features.questionnaires.valueSets.vme.url, + valueSetData: getConfig().features.questionnaires.valueSets.vme.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_007005', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'VME :' }] + } + }, + { + valueKey: 'maturationCorticotherapie', + type: 'autocomplete', + label: 'Corticothérapie pour maturation fœtale faite', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_007006', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Corticothérapie pour maturation foetal faite :' }] + } + }, + { + valueKey: 'chirurgicalGesture', + type: 'autocomplete', + label: 'Type de geste ou de chirurgie', + noOptionsText: 'Veuillez entrer un type de geste ou de chirurgie', + valueSetId: getConfig().features.questionnaires.valueSets.chirurgicalGesture.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004623', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Type de geste ou de chirurgie :' }] + } + } + ] + }, + { + title: 'SYNTHESE', + defaulCollapsed: true, + items: [ + { + valueKey: 'childbirth', + type: 'autocomplete', + label: 'Accouchement', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_007025', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Accouchement :' }] + } + }, + { + valueKey: 'hospitalChildBirthPlace', + type: 'autocomplete', + label: "Accouchement à la maternité de l'hospitalisation", + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_004801', + type: 'valueBoolean' + }, + chipDisplayMethodExtraArgs: [ + { type: 'string', value: "Accouchement à la maternité de l'hospitalisation :" } + ] + } + }, + { + valueKey: 'otherHospitalChildBirthPlace', + type: 'autocomplete', + label: 'Accouchement dans un autre hôpital', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanFields.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_004803', + type: 'valueBoolean' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Accouchement dans un autre hôpital :' }] + } + }, + { + valueKey: 'homeChildBirthPlace', + type: 'autocomplete', + label: 'Accouchement à domicile', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanFields.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_004805', + type: 'valueBoolean' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Accouchement à domicile :' }] + } + }, + { + valueKey: 'childbirthMode', + type: 'autocomplete', + label: 'Mode de mise en travail', + noOptionsText: 'Veuillez entrer un mode de mise en travail', + valueSetId: getConfig().features.questionnaires.valueSets.childBirthMode.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004830', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Mode de mise en travail :' }] + } + }, + { + valueKey: 'maturationReason', + type: 'autocomplete', + label: 'Motif(s) de maturation / déclenchement', + noOptionsText: 'Veuillez entrer un motif de maturation / déclenchement', + valueSetId: getConfig().features.questionnaires.valueSets.maturationReason.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004831', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Motif(s) de maturation / déclenchement :' }] + } + }, + { + valueKey: 'maturationModality', + type: 'autocomplete', + label: 'Modalités de maturation cervicale initiale', + noOptionsText: 'Veuillez entrer une modalité de maturation cervicale initiale', + valueSetId: getConfig().features.questionnaires.valueSets.maturationModality.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004833', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Modalités de maturation cervicale initiale :' }] + } + }, + { + valueKey: 'imgIndication', + type: 'autocomplete', + label: "Indication de l'IMG", + noOptionsText: "Veuillez entrer une indication de l'IMG", + valueSetId: getConfig().features.questionnaires.valueSets.imgIndication.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004359', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Indication de l'IMG :" }] + } + }, + { + valueKey: 'laborOrCesareanEntry', + type: 'autocomplete', + label: "Présentation à l'entrée en travail ou en début de césarienne", + noOptionsText: "Veuillez entrer une présentation à l'entrée en travail ou en début de césarienne", + valueSetId: getConfig().features.questionnaires.valueSets.laborOrCesareanEntry.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004842', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [ + { type: 'string', value: "Présentation à l'entrée en travail ou en début de césarienne :" } + ] + } + }, + { + valueKey: 'pathologyDuringLabor', + type: 'autocomplete', + label: 'Pathologie pendant le travail', + noOptionsText: 'Veuillez entrer une pathologie pendant le travail', + valueSetId: getConfig().features.questionnaires.valueSets.pathologyDuringLabor.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004859', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Pathologie pendant le travail :' }] + } + }, + { + valueKey: 'obstetricalGestureDuringLabor', + type: 'autocomplete', + label: 'Geste ou manoeuvre obstétricale pendant le travail', + noOptionsText: 'Veuillez entrer un geste ou manoeuvre obstétricale pendant le travail', + valueSetId: getConfig().features.questionnaires.valueSets.obstetricalGestureDuringLabor.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004864', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [ + { type: 'string', value: 'Geste ou manoeuvre obstétricale pendant le travail :' } + ] + } + } + ] + }, + { + title: 'ANALGESIE / ANESTHESIE', + defaulCollapsed: true, + items: [ + { + valueKey: 'analgesieType', + type: 'autocomplete', + label: 'Analgésie / Anesthésie - Type', + noOptionsText: "Veuillez entrer un type d'Analgésie / Anesthésie - Type", + valueSetId: getConfig().features.questionnaires.valueSets.analgesieType.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004901', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'ANALGESIE / ANESTHESIE - type :' }] + } + } + ] + }, + { + title: 'ACCOUCHEMENT ET NAISSANCE', + defaulCollapsed: true, + items: [ + { + valueKey: 'birthDeliveryDate', + type: 'calendarRange', + label: "Date/heure de l'accouchement", + errorType: 'INCOHERENT_AGE_ERROR', + buildInfo: { + fhirKey: { + id: 'F_MATER_004961', + type: 'valueDateTime' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Date/heure de l'accouchement :" }] + } + }, + { + valueKey: 'birthDeliveryWeeks', + type: 'numberAndComparator', + label: 'Accouchement - Terme - Semaines', + buildInfo: { + fhirKey: { + id: 'F_MATER_004962', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Nombre de semaines (Accouchement - Terme)' }] + } + }, + { + valueKey: 'birthDeliveryDays', + type: 'numberAndComparator', + label: 'Accouchement - Terme - Jours', + buildInfo: { + fhirKey: { + id: 'F_MATER_004963', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Nombre de jours (Accouchement - Terme)' }] + } + }, + { + valueKey: 'birthDeliveryWay', + type: 'autocomplete', + label: 'Voie d’accouchement', + noOptionsText: 'Veuillez entrer une voie d’accouchement', + valueSetId: getConfig().features.questionnaires.valueSets.birthDeliveryWay.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004980', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Voie d’accouchement :' }] + } + }, + { + valueKey: 'instrumentType', + type: 'autocomplete', + label: "Type d'instrument", + noOptionsText: "Veuillez entrer un type d'instrument", + valueSetId: getConfig().features.questionnaires.valueSets.instrumentType.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004984', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Type d'instrument :" }] + } + }, + { + valueKey: 'cSectionModality', + type: 'autocomplete', + label: 'Modalités de la césarienne', + noOptionsText: 'Veuillez entrer une modalité de la césarienne', + valueSetId: getConfig().features.questionnaires.valueSets.cSectionModality.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004990', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Modalités de la césarienne :' }] + } + }, + { + valueKey: 'presentationAtDelivery', + type: 'autocomplete', + label: "Présentation à l'accouchement", + noOptionsText: "Veuillez entrer une présentation à l'accouchement", + valueSetId: getConfig().features.questionnaires.valueSets.presentationAtDelivery.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_004999', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Présentation à l'accouchement :" }] + } + }, + { + valueKey: 'birthMensurationsGrams', + type: 'numberAndComparator', + label: 'Mensurations naissance - Poids (g)', + buildInfo: { + fhirKey: { + id: 'F_MATER_005033', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Mensurations naissance - Poids (g) :' }] + } + }, + { + valueKey: 'birthMensurationsPercentil', + type: 'numberAndComparator', + floatValues: true, + label: 'Mensurations naissance - Poids (percentile)', + buildInfo: { + fhirKey: { + id: 'F_MATER_005034', + type: 'valueDecimal' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Mensurations naissance - Poids (percentile) :' }] + } + }, + { + valueKey: 'apgar1', + type: 'numberAndComparator', + label: 'Score Apgar - 1 min', + buildInfo: { + fhirKey: { + id: 'F_MATER_005051', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Score Apgar - 1 min :' }] + } + }, + { + valueKey: 'apgar3', + type: 'numberAndComparator', + label: 'Score Apgar - 3 min', + buildInfo: { + fhirKey: { + id: 'F_MATER_005052', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Score Apgar - 3 min :' }] + } + }, + { + valueKey: 'apgar5', + type: 'numberAndComparator', + label: 'Score Apgar - 5 min', + buildInfo: { + fhirKey: { + id: 'F_MATER_005053', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Score Apgar - 5 min :' }] + } + }, + { + valueKey: 'apgar10', + type: 'numberAndComparator', + label: 'Score Apgar - 10 min', + buildInfo: { + fhirKey: { + id: 'F_MATER_005054', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Score Apgar - 10 min :' }] + } + }, + { + valueKey: 'arterialPhCord', + type: 'numberAndComparator', + floatValues: true, + label: 'pH artériel au cordon', + buildInfo: { + fhirKey: { + id: 'F_MATER_005060', + type: 'valueDecimal' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'pH artériel au cordon :' }] + } + }, + { + valueKey: 'arterialCordLactates', + type: 'numberAndComparator', + floatValues: true, + label: 'Lactate artériel au cordon (mmol/L)', + buildInfo: { + fhirKey: { + id: 'F_MATER_005061', + type: 'valueDecimal' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Lactate artériel au cordon (mmol/L) :' }] + } + }, + { + valueKey: 'birthStatus', + type: 'autocomplete', + label: 'Statut vital à la naissance', + noOptionsText: 'Veuillez entrer un statut vital à la naissance', + valueSetId: getConfig().features.questionnaires.valueSets.birthStatus.url, + valueSetData: getConfig().features.questionnaires.valueSets.birthStatus.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_007030', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut vital à la naissance :' }] + } + }, + { + valueKey: 'postpartumHemorrhage', + type: 'autocomplete', + label: 'Hémorragie du post-partum', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_007031', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Hémorragie du post-partum :' }] + } + }, + { + valueKey: 'conditionPerineum', + type: 'autocomplete', + label: 'État du périnée', + noOptionsText: 'Veuillez entrer un état du périnée', + valueSetId: getConfig().features.questionnaires.valueSets.conditionPerineum.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_005151', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'État du périnée :' }] + } + }, + { + valueKey: 'exitPlaceType', + type: 'autocomplete', + label: 'Type de lieu de sortie', + noOptionsText: 'Veuillez entrer un type de lieu de sortie', + valueSetId: getConfig().features.questionnaires.valueSets.exitPlaceType.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_005301', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Type de lieu de sortie :' }] + } + } + ] + }, + { + title: 'SUITES DE COUCHES', + defaulCollapsed: true, + items: [ + { + valueKey: 'feedingType', + type: 'autocomplete', + label: "Type d'allaitement", + noOptionsText: "Veuillez entrer un type d'allaitement", + valueSetId: getConfig().features.questionnaires.valueSets.feedingType.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_005507', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Type d'allaitement :" }] + } + }, + { + valueKey: 'complication', + type: 'autocomplete', + label: 'Aucune complication', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanFields.data, + buildInfo: { + fhirKey: { + id: 'F_MATER_005556', + type: 'valueBoolean' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Aucune complication :' }] + } + } + ] + }, + { + title: 'SORTIE', + defaulCollapsed: true, + items: [ + { + valueKey: 'exitFeedingMode', + type: 'autocomplete', + label: "Mode d'allaitement à la sortie", + noOptionsText: "Veuillez entrer un mode d'allaitement à la sortie", + valueSetId: getConfig().features.questionnaires.valueSets.exitFeedingMode.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_005834', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Mode d'allaitement à la sortie :" }] + } + }, + { + valueKey: 'exitDiagnostic', + type: 'autocomplete', + label: 'Diagnostic de sortie', + noOptionsText: 'Veuillez entrer un diagnostic de sortie', + valueSetId: getConfig().features.questionnaires.valueSets.exitDiagnostic.url, + buildInfo: { + fhirKey: { + id: 'F_MATER_005903', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Diagnostic de sortie :' }] + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/IPPForm.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/IPPForm.ts new file mode 100644 index 000000000..454b67cf0 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/IPPForm.ts @@ -0,0 +1,43 @@ +import { CriteriaType, IppParamsKeys, ResourceType } from 'types/requestCriterias' +import { CommonCriteriaData, CriteriaForm } from '../CriteriaForm/types' + +export type IPPListDataType = CommonCriteriaData & { + type: CriteriaType.IPP_LIST + search: string +} + +export const form: () => CriteriaForm = () => ({ + label: "Liste d'IPP", + initialData: { + type: CriteriaType.IPP_LIST, + title: "Liste d'IPP", + isInclusive: true, + encounterService: null, + search: '' + }, + buildInfo: { + criteriaType: CriteriaType.IPP_LIST, + resourceType: ResourceType.IPP_LIST // TODO should be ResourceType.PATIENT + }, + errorMessages: {}, + itemSections: [ + { + items: [ + { + valueKey: 'search', + type: 'textWithRegex', + label: "Recherche par uid d'étude", + regex: '(?:^|\\D+)?(8\\d{9})(?:$|\\D+)', + placeholder: "Ajouter une liste d'IPP", + extractValidValues: true, + multiline: true, + buildInfo: { + fhirKey: IppParamsKeys.IPP_LIST_FHIR, + chipDisplayMethod: 'idListLabel', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'patient' }] + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/ImagingForm.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/ImagingForm.ts new file mode 100644 index 000000000..ef9a2682d --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/ImagingForm.ts @@ -0,0 +1,313 @@ +import { Comparators, CriteriaType, ImagingParamsKeys, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + NewDurationRangeType, + NumberAndComparatorDataType, + WithEncounterDateDataType, + WithEncounterStatusDataType +} from '../CriteriaForm/types' +import { LabelObject } from 'types/searchCriterias' +import { getConfig } from 'config' +import { SourceType } from 'types/scope' + +export type ImagingDataType = CommonCriteriaData & + WithEncounterDateDataType & + WithEncounterStatusDataType & { + type: CriteriaType.IMAGING + occurrence: NumberAndComparatorDataType + studyDate: NewDurationRangeType | null + studyModalities: LabelObject[] | null + studyDescription: string + studyProcedure: string + numberOfSeries: NumberAndComparatorDataType + numberOfIns: NumberAndComparatorDataType + withDocument: LabelObject[] + daysOfDelay: string | null + studyUid: string + seriesDate: NewDurationRangeType | null + seriesDescription: string + seriesProtocol: string + seriesModalities: LabelObject[] | null + seriesUid: string + } + +export const form: () => CriteriaForm = () => ({ + label: "Critère d'Imagerie", + initialData: { + title: "Critère d'Imagerie", + type: CriteriaType.IMAGING, + isInclusive: true, + occurrence: { + value: 1, + comparator: Comparators.GREATER_OR_EQUAL + }, + encounterService: null, + encounterStatus: [], + encounterStartDate: null, + encounterEndDate: null, + studyDate: null, + studyModalities: [], + studyDescription: '', + studyProcedure: '', + numberOfSeries: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + numberOfIns: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + withDocument: [{ id: 'NONE', label: 'Aucun' }], + daysOfDelay: null, + studyUid: '', + seriesDate: null, + seriesDescription: '', + seriesProtocol: '', + seriesModalities: [], + seriesUid: '' + }, + errorMessages: { + INCOHERENT_AGE_ERROR: "Erreur de cohérence d'âge", + SEARCHINPUT_ERROR: 'Erreur de saisie de recherche', + UID_ERROR: 'Erreur de UID', + ADVANCED_INPUTS_ERROR: "Erreur d'entrées avancées" + }, + buildInfo: { + criteriaType: CriteriaType.IMAGING, + resourceType: ResourceType.IMAGING, + defaultFilter: 'patient.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée', + buildInfo: { + fhirKey: ImagingParamsKeys.ENCOUNTER_STATUS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite associée' }] + } + } + ] + }, + { + title: 'Critères liés à une étude', + defaulCollapsed: true, + items: [ + { + valueKey: 'studyDate', + type: 'calendarRange', + label: "Date de l'étude", + errorType: 'INCOHERENT_AGE_ERROR', + buildInfo: { + fhirKey: ImagingParamsKeys.DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Date de l'étude : " }] + } + }, + { + valueKey: 'studyModalities', + type: 'autocomplete', + label: 'Modalités', + valueSetId: getConfig().features.imaging.valueSets.imagingModalities.url, + noOptionsText: 'Veuillez entrer des modalités', + buildInfo: { + fhirKey: ImagingParamsKeys.MODALITY, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Modalités d'étude :" }] + } + }, + { + valueKey: 'studyDescription', + type: 'textWithCheck', + label: 'Rechercher dans les descriptions', + placeholder: 'Rechercher dans les descriptions', + errorType: 'SEARCHINPUT_ERROR', + buildInfo: { + fhirKey: ImagingParamsKeys.STUDY_DESCRIPTION, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Description de l'étude : " }] + } + }, + { + valueKey: 'studyProcedure', + type: 'textWithCheck', + label: 'Rechercher dans les codes procédures', + placeholder: 'Rechercher dans les codes procédures', + errorType: 'SEARCHINPUT_ERROR', + buildInfo: { + fhirKey: ImagingParamsKeys.STUDY_PROCEDURE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Code procédure de l'étude : " }] + } + }, + { + valueKey: 'numberOfSeries', + type: 'numberAndComparator', + label: 'Nombre de séries', + withHierarchyInfo: true, + buildInfo: { + fhirKey: ImagingParamsKeys.NB_OF_SERIES, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Nombre de séries : ' }] + } + }, + { + valueKey: 'numberOfIns', + type: 'numberAndComparator', + label: "Nombre d'instances", + withHierarchyInfo: true, + buildInfo: { + fhirKey: ImagingParamsKeys.NB_OF_INS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'instances : " }] + } + }, + { + valueKey: 'withDocument', + type: 'autocomplete', + label: 'Méthode de rattachement à un document', + valueSetId: 'documentAttachementMethod', + singleChoice: true, + valueSetData: getConfig().features.imaging.valueSets.documentAttachementMethod.data, + noOptionsText: 'Veuillez entrer une méthode de rattachement', + buildInfo: { + fhirKey: ImagingParamsKeys.WITH_DOCUMENT, + buildMethod: 'buildWithDocument', + buildMethodExtraArgs: [{ type: 'reference', value: 'daysOfDelay' }], + chipDisplayMethodExtraArgs: [{ type: 'reference', value: 'daysOfDelay' }] + } + }, + { + valueKey: 'daysOfDelay', + type: 'number', + label: 'Plage de jours', + errorType: 'INCOHERENT_AGE_ERROR', + displayCondition: (data) => (data.withDocument as LabelObject[])?.at(0)?.id === 'INFERENCE_TEMPOREL' + }, + { + valueKey: 'studyUid', + type: 'textWithRegex', + label: "Recherche par uid d'étude", + regex: '[^0-9.,]', + inverseCheck: true, + checkErrorMessage: 'Seuls les chiffres, points, ou les virgules sont autorisés.', + placeholder: "Ajouter une liste d'uid séparés par des virgules", + multiline: true, + buildInfo: { + fhirKey: ImagingParamsKeys.STUDY_UID, + buildMethodExtraArgs: [ + { type: 'string', value: getConfig().features.imaging.extensions.imagingStudyUidUrl } + ], + chipDisplayMethod: 'idListLabel', + chipDisplayMethodExtraArgs: [{ type: 'string', value: "uuid d'étude" }] + } + } + ] + }, + { + title: 'Critères liés à une série', + defaulCollapsed: true, + items: [ + { + valueKey: 'seriesDate', + type: 'calendarRange', + label: 'Date de la série', + errorType: 'INCOHERENT_AGE_ERROR', + buildInfo: { + fhirKey: ImagingParamsKeys.SERIES_DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de la série : ' }] + } + }, + { + valueKey: 'seriesDescription', + type: 'textWithCheck', + label: 'Rechercher dans les descriptions', + placeholder: 'Rechercher dans les descriptions', + errorType: 'SEARCHINPUT_ERROR', + buildInfo: { + fhirKey: ImagingParamsKeys.SERIES_DESCRIPTION, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Description de la série : ' }] + } + }, + { + valueKey: 'seriesProtocol', + type: 'textWithCheck', + label: 'Rechercher dans les protocoles', + placeholder: 'Rechercher dans les protocoles', + errorType: 'SEARCHINPUT_ERROR', + buildInfo: { + fhirKey: ImagingParamsKeys.SERIES_PROTOCOL, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Protocole de la série : ' }] + } + }, + { + valueKey: 'seriesModalities', + type: 'autocomplete', + label: 'Modalités', + valueSetId: getConfig().features.imaging.valueSets.imagingModalities.url, + noOptionsText: 'Veuillez entrer des modalités', + buildInfo: { + fhirKey: ImagingParamsKeys.SERIES_MODALITIES, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Modalités de la série : ' }] + } + }, + { + valueKey: 'seriesUid', + type: 'textWithRegex', + label: 'Recherche par uid de série', + regex: '[^0-9.,]', + checkErrorMessage: 'Seuls les chiffres, points, ou les virgules sont autorisés.', + placeholder: "Ajouter une liste d'uid séparés par des virgules", + multiline: true, + buildInfo: { + fhirKey: ImagingParamsKeys.SERIES_UID, + chipDisplayMethod: 'idListLabel', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'uuid de série' }] + } + } + ] + }, + { + title: 'Options avancées', + defaulCollapsed: true, + items: [ + { + valueKey: 'encounterService', + label: 'Unité exécutrice', + type: 'executiveUnit', + sourceType: SourceType.IMAGING, + buildInfo: { + fhirKey: ImagingParamsKeys.EXECUTIVE_UNITS + } + }, + { + valueKey: 'encounterStartDate', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + label: 'Début de prise en charge', + labelAltStyle: true, + extraLabel: () => 'Prise en charge', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-start', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de début de prise en charge' }] + } + }, + { + valueKey: 'encounterEndDate', + type: 'calendarRange', + label: 'Fin de prise en charge', + labelAltStyle: true, + errorType: 'ADVANCED_INPUTS_ERROR', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: 'encounter.period-end', + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de fin de prise en charge' }] + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/MedicationForm.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/MedicationForm.ts new file mode 100644 index 000000000..15a4a6135 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/MedicationForm.ts @@ -0,0 +1,246 @@ +import { + AdministrationParamsKeys, + PrescriptionParamsKeys, + Comparators, + CriteriaType, + ResourceType +} from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + NewDurationRangeType, + WithEncounterDateDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { LabelObject } from 'types/searchCriterias' +import { SourceType } from 'types/scope' +import { getConfig } from 'config' + +export type MedicationDataType = CommonCriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterDateDataType & + WithEncounterStatusDataType & { + code: LabelObject[] | null + administration: LabelObject[] | null + type: CriteriaType.MEDICATION_REQUEST | CriteriaType.MEDICATION_ADMINISTRATION + prescriptionType: LabelObject[] | null + endOccurrence: NewDurationRangeType | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'Médicaments', + initialData: { + type: CriteriaType.MEDICATION_REQUEST, + title: 'Critère de médicament', + isInclusive: true, + occurrence: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + encounterService: null, + startOccurrence: null, + encounterStartDate: null, + encounterEndDate: null, + encounterStatus: [], + code: null, + administration: null, + prescriptionType: null, + endOccurrence: null + }, + errorMessages: { + INCOHERENT_VALUE_ERROR: 'La valeur minimale ne peut pas être supérieure à la valeur maximale.', + INVALID_VALUE_ERROR: 'Veuillez entrer un nombre valide.', + MISSING_VALUE_ERROR: 'Veuillez entrer 2 valeurs avec ce comparateur.', + ADVANCED_INPUTS_ERROR: 'Erreur dans les options avancées.', + NO_ERROR: '' + }, + buildInfo: { + criteriaType: [CriteriaType.MEDICATION_ADMINISTRATION, CriteriaType.MEDICATION_REQUEST], + resourceType: [ResourceType.MEDICATION_ADMINISTRATION, ResourceType.MEDICATION_REQUEST], + defaultFilter: 'subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: 'Occurrence', + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'reference', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'type', + type: 'radioChoice', + label: 'Type de médication', + choices: [ + { id: CriteriaType.MEDICATION_REQUEST, label: 'Prescription' }, + { id: CriteriaType.MEDICATION_ADMINISTRATION, label: 'Administration' } + ], + buildInfo: { + chipDisplayMethod: 'altArgs', + chipDisplayMethodExtraArgs: [ + { type: 'method', value: 'raw' }, + { type: 'reference', value: 'type' }, + { type: 'string', value: CriteriaType.MEDICATION_REQUEST }, + { type: 'string', value: 'Prescription' }, + { type: 'string', value: 'Administration' } + ] + } + }, + { + valueKey: 'code', + type: 'codeSearch', + label: 'Code(s) sélectionné(s)', + noOptionsText: 'Veuillez entrer un code de médicament', + valueSetIds: [ + getConfig().features.medication.valueSets.medicationAtc.url, + getConfig().features.medication.valueSets.medicationUcd.url + ], + buildInfo: { + fhirKey: PrescriptionParamsKeys.CODE, + buildMethodExtraArgs: [ + { type: 'string', value: getConfig().features.medication.valueSets.medicationAtc.url }, + { type: 'boolean', value: true } + ] + } + }, + { + valueKey: 'prescriptionType', + type: 'autocomplete', + label: 'Type de prescription', + valueSetId: getConfig().features.medication.valueSets.medicationPrescriptionTypes.url, + noOptionsText: 'Veuillez entrer un type de prescription', + displayCondition: (data) => data.type === CriteriaType.MEDICATION_REQUEST, + buildInfo: { + fhirKey: PrescriptionParamsKeys.PRESCRIPTION_TYPES, + buildMethodExtraArgs: [ + { type: 'method', value: 'buildLabelObject' }, + { type: 'reference', value: 'type' }, + { type: 'string', value: ResourceType.MEDICATION_ADMINISTRATION } + ], + buildMethod: 'skipIf' + } + }, + { + valueKey: 'administration', + type: 'autocomplete', + label: "Voie d'administration", + valueSetId: getConfig().features.medication.valueSets.medicationAdministrations.url, + noOptionsText: "Veuillez entrer une voie d'administration", + buildInfo: { + fhirKey: { + main: PrescriptionParamsKeys.PRESCRIPTION_ROUTES, + alt: AdministrationParamsKeys.ADMINISTRATION_ROUTES, + value1: { type: 'reference', value: 'type' }, + value2: { type: 'string', value: ResourceType.MEDICATION_REQUEST } + } + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée', + buildInfo: { + fhirKey: { + main: PrescriptionParamsKeys.ENCOUNTER_STATUS, + alt: AdministrationParamsKeys.ENCOUNTER_STATUS, + value1: { type: 'reference', value: 'type' }, + value2: { type: 'string', value: ResourceType.MEDICATION_REQUEST } + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite associée' }] + } + } + ] + }, + { + title: 'Options avancées', + defaulCollapsed: true, + items: [ + { + valueKey: 'encounterService', + label: 'Unité exécutrice', + type: 'executiveUnit', + sourceType: SourceType.MEDICATION, + buildInfo: { + fhirKey: { + main: PrescriptionParamsKeys.EXECUTIVE_UNITS, + alt: AdministrationParamsKeys.EXECUTIVE_UNITS, + value1: { type: 'reference', value: 'type' }, + value2: { type: 'string', value: ResourceType.MEDICATION_REQUEST } + } + } + }, + { + valueKey: 'encounterStartDate', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + label: 'Début de prise en charge', + labelAltStyle: true, + extraLabel: () => 'Prise en charge', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: { + main: 'encounter.period-start', + alt: 'context.period-start', + value1: { type: 'reference', value: 'type' }, + value2: { type: 'string', value: ResourceType.MEDICATION_REQUEST } + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de début de prise en charge' }] + } + }, + { + valueKey: 'encounterEndDate', + type: 'calendarRange', + label: 'Fin de prise en charge', + labelAltStyle: true, + errorType: 'ADVANCED_INPUTS_ERROR', + withOptionIncludeNull: true, + buildInfo: { + fhirKey: { + main: 'encounter.period-end', + alt: 'context.period-end', + value1: { type: 'reference', value: 'type' }, + value2: { type: 'string', value: ResourceType.MEDICATION_REQUEST } + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de fin de prise en charge' }] + } + }, + { + valueKey: 'startOccurrence', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + extraLabel: () => 'Date de classement en GHM', + buildInfo: { + fhirKey: { + main: PrescriptionParamsKeys.DATE, + alt: AdministrationParamsKeys.DATE, + value1: { type: 'reference', value: 'type' }, + value2: { type: 'string', value: ResourceType.MEDICATION_REQUEST } + }, + chipDisplayMethod: 'altArgs', + chipDisplayMethodExtraArgs: [ + { type: 'method', value: 'calendarRange' }, + { type: 'reference', value: 'type' }, + { type: 'string', value: ResourceType.MEDICATION_REQUEST }, + { type: 'string', value: 'Date de début de prescription' }, + { type: 'string', value: "Date de début d'administration" } + ] + } + }, + { + valueKey: 'endOccurrence', + type: 'calendarRange', + errorType: 'ADVANCED_INPUTS_ERROR', + extraLabel: () => 'Date de classement en GHM', + displayCondition: (data) => data.type === CriteriaType.MEDICATION_REQUEST, + buildInfo: { + fhirKey: PrescriptionParamsKeys.END_DATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de fin de prescription' }] + } + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/PregnancyForm.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/PregnancyForm.ts new file mode 100644 index 000000000..288bb8969 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/PregnancyForm.ts @@ -0,0 +1,305 @@ +import { Comparators, CriteriaType, QuestionnaireResponseParamsKeys, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + NewDurationRangeType, + NumberAndComparatorDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { FormNames, LabelObject } from 'types/searchCriterias' +import { SourceType } from 'types/scope' +import { getConfig } from 'config' + +export type PregnancyDataType = CommonCriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterStatusDataType & { + type: CriteriaType.PREGNANCY + pregnancyDate: NewDurationRangeType | null + pregnancyMode: LabelObject[] | null + foetus: NumberAndComparatorDataType + parity: NumberAndComparatorDataType + maternalRisks: LabelObject[] | null + maternalRisksPrecision: string + risksRelatedToObstetricHistory: LabelObject[] | null + risksRelatedToObstetricHistoryPrecision: string + risksOrComplicationsOfPregnancy: LabelObject[] | null + risksOrComplicationsOfPregnancyPrecision: string + corticotherapie: LabelObject[] | null + prenatalDiagnosis: LabelObject[] | null + ultrasoundMonitoring: LabelObject[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'Critère de Fin de Grossesse', + initialData: { + type: CriteriaType.PREGNANCY, + title: 'Critère de fin de grossesse', + isInclusive: true, + occurrence: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + encounterService: null, + startOccurrence: null, + encounterStatus: [], + pregnancyDate: null, + pregnancyMode: null, + foetus: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + parity: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + maternalRisks: null, + maternalRisksPrecision: '', + risksRelatedToObstetricHistory: null, + risksRelatedToObstetricHistoryPrecision: '', + risksOrComplicationsOfPregnancy: null, + risksOrComplicationsOfPregnancyPrecision: '', + corticotherapie: null, + prenatalDiagnosis: null, + ultrasoundMonitoring: null + }, + errorMessages: { + INCOHERENT_VALUE_ERROR: 'La valeur minimale ne peut pas être supérieure à la valeur maximale.', + INVALID_VALUE_ERROR: 'Veuillez entrer un nombre valide.', + MISSING_VALUE_ERROR: 'Veuillez entrer 2 valeurs avec ce comparateur.', + ADVANCED_INPUTS_ERROR: 'Erreur dans les options avancées.', + NO_ERROR: '' + }, + buildInfo: { + criteriaType: CriteriaType.PREGNANCY, + resourceType: ResourceType.QUESTIONNAIRE_RESPONSE, + defaultFilter: `subject.active=true&questionnaire.name=${FormNames.PREGNANCY}&status=in-progress,completed`, + subType: FormNames.PREGNANCY + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: 'Occurrence', + allowBetween: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurences" }] + } + }, + { + valueKey: 'encounterService', + type: 'executiveUnit', + label: 'Service de rencontre', + sourceType: SourceType.MATERNITY, + buildInfo: { + fhirKey: { + id: QuestionnaireResponseParamsKeys.EXECUTIVE_UNITS, + type: 'valueCoding' + } + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée', + buildInfo: { + fhirKey: QuestionnaireResponseParamsKeys.ENCOUNTER_STATUS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Statut de la visite associée' }] + } + } + ] + }, + { + title: 'Renseignements sur la grossesse', + items: [ + { + valueKey: 'pregnancyDate', + type: 'calendarRange', + label: 'Date de début de grossesse', + withOptionIncludeNull: true, + errorType: 'INCOHERENT_VALUE_ERROR', + buildInfo: { + fhirKey: { + id: 'F_MATER_001010', + type: 'valueDate' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Date de début de grossesse : ' }] + } + }, + { + valueKey: 'pregnancyMode', + type: 'autocomplete', + label: "Mode d'obtention de la grossesse", + valueSetId: getConfig().features.questionnaires.valueSets.pregnancyMode.url, + noOptionsText: "Veuillez entrer un mode d'obtention de la grossesse", + buildInfo: { + fhirKey: { + id: 'F_MATER_001014', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Mode de grossesse : ' }] + } + }, + { + valueKey: 'foetus', + type: 'numberAndComparator', + label: 'Nombre de fœtus', + allowBetween: true, + buildInfo: { + fhirKey: { + id: 'F_MATER_001017', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Nombre de fœtus' }] + } + }, + { + valueKey: 'parity', + type: 'numberAndComparator', + label: 'Parité', + allowBetween: true, + buildInfo: { + fhirKey: { + id: 'F_MATER_001192', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Parité' }] + } + }, + { + valueKey: 'maternalRisks', + type: 'autocomplete', + label: 'Risques liés aux antécédents maternels', + valueSetId: getConfig().features.questionnaires.valueSets.maternalRisks.url, + noOptionsText: 'Veuillez entrer un risque lié aux antécédents maternels', + buildInfo: { + fhirKey: { + id: 'F_MATER_001361', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Risques maternels :' }] + } + }, + { + valueKey: 'maternalRisksPrecision', + type: 'textWithCheck', + label: 'Risques liés aux antécédents maternels - Précision autre', + placeholder: 'Précision autre', + errorType: 'SEARCHINPUT_ERROR', + buildInfo: { + fhirKey: { + id: 'F_MATER_001362', + type: 'valueString' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Précision sur les risques maternels :' }] + } + }, + { + valueKey: 'risksRelatedToObstetricHistory', + type: 'autocomplete', + label: 'Risques liés aux antécédents obstétricaux', + valueSetId: getConfig().features.questionnaires.valueSets.risksRelatedToObstetricHistory.url, + noOptionsText: 'Veuillez entrer un risque lié aux antécédents obstétricaux', + buildInfo: { + fhirKey: { + id: 'F_MATER_001363', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Risques liés aux antécédents obstétricaux :' }] + } + }, + { + valueKey: 'risksRelatedToObstetricHistoryPrecision', + type: 'textWithCheck', + label: 'Risques liés aux antécédents obstétricaux - précision autre', + placeholder: 'Précision autre', + errorType: 'SEARCHINPUT_ERROR', + buildInfo: { + fhirKey: { + id: 'F_MATER_001364', + type: 'valueString' + }, + chipDisplayMethodExtraArgs: [ + { type: 'string', value: 'Précision sur les risques liés aux antécédents obstétricaux :' } + ] + } + } + ] + }, + { + title: 'Suivi de grossesse', + items: [ + { + valueKey: 'ultrasoundMonitoring', + type: 'autocomplete', + label: 'Suivi échographique', + valueSetId: getConfig().features.questionnaires.valueSets.booleanFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanFields.data, + noOptionsText: 'Veuillez entrer "oui" ou "non"', + buildInfo: { + fhirKey: { + id: 'F_MATER_001552', + type: 'valueBoolean' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Suivi échographique :' }] + } + }, + { + valueKey: 'corticotherapie', + type: 'autocomplete', + label: 'Corticothérapie pour maturation pulmonaire fœtale', + valueSetId: getConfig().features.questionnaires.valueSets.booleanFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanFields.data, + noOptionsText: 'Veuillez entrer "oui" ou "non"', + buildInfo: { + fhirKey: { + id: 'F_MATER_001597', + type: 'valueBoolean' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Corticothérapie :' }] + } + }, + { + valueKey: 'risksOrComplicationsOfPregnancy', + type: 'autocomplete', + label: 'Risques ou complications de la grossesse', + valueSetId: getConfig().features.questionnaires.valueSets.risksOrComplicationsOfPregnancy.url, + noOptionsText: 'Veuillez entrer un risque ou complication de la grossesse', + buildInfo: { + fhirKey: { + id: 'F_MATER_001631', + type: 'valueCoding' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Risques ou complications de la grossesse :' }] + } + }, + { + valueKey: 'risksOrComplicationsOfPregnancyPrecision', + type: 'textWithCheck', + label: 'Risques ou complications de la grossesse - Précision autre', + placeholder: 'Précision autre', + errorType: 'SEARCHINPUT_ERROR', + buildInfo: { + fhirKey: { + id: 'F_MATER_001632', + type: 'valueString' + }, + chipDisplayMethodExtraArgs: [ + { type: 'string', value: 'Précision sur les risques ou complications de la grossesse :' } + ] + } + }, + { + valueKey: 'prenatalDiagnosis', + type: 'autocomplete', + label: 'Grossesse suivie au diagnostic prénatal', + valueSetId: getConfig().features.questionnaires.valueSets.booleanFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanFields.data, + noOptionsText: 'Veuillez entrer "oui" ou "non"', + buildInfo: { + fhirKey: { + id: 'F_MATER_001661', + type: 'valueBoolean' + } + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Diagnostic prénatal :' }] + } + ] + } + ] +}) diff --git a/src/components/CreationCohort/Requeteur.tsx b/src/components/CreationCohort/Requeteur.tsx index c1257e34c..a50a7c88c 100644 --- a/src/components/CreationCohort/Requeteur.tsx +++ b/src/components/CreationCohort/Requeteur.tsx @@ -16,12 +16,13 @@ import { CurrentSnapshot } from 'types' import criteriaList from './DataList_Criteria' -import { getDataFromFetch, cleanNominativeCriterias } from 'utils/cohortCreation' +import { cleanNominativeCriterias, fetchCriteriasCodes } from 'utils/cohortCreation' import useStyles from './styles' import services from 'services/aphp' import { setCriteriaData } from 'state/criteria' import { AppConfig } from 'config' +import { initValueSets, updateCache } from 'state/valueSets' const Requeteur = () => { const { @@ -37,7 +38,7 @@ const Requeteur = () => { json = '', allowSearchIpp = false } = useAppSelector((state) => state.cohortCreation.request || {}) - const criteriaData = useAppSelector((state) => state.cohortCreation.criteria || {}) + const valueSets = useAppSelector((state) => state.valueSets) const config = useContext(AppConfig) const params = useParams<{ requestId: string @@ -53,6 +54,7 @@ const Requeteur = () => { const [requestLoading, setRequestLoading] = useState(0) const [criteriaLoading, setCriteriaLoading] = useState(0) + const [valueSetsLoading, setValueSetsLoading] = useState(true) const isRendered = useRef(false) const _fetchRequest = useCallback(async () => { @@ -81,7 +83,8 @@ const Requeteur = () => { setCriteriaLoading((criteriaLoading) => criteriaLoading + 1) } try { - const criteriaCache = await getDataFromFetch(criteriaList(), selectedCriteria, criteriaData.cache) + const criteriaCodesCache = await fetchCriteriasCodes(criteriaList(), selectedCriteria, valueSets.cache) + dispatch(updateCache(criteriaCodesCache)) const allowMaternityForms = selectedPopulation?.every((population) => population?.access === 'Nominatif') const questionnairesEnabled = config.features.questionnaires.enabled @@ -100,8 +103,7 @@ const Requeteur = () => { color: allowMaternityForms && questionnairesEnabled ? '#0063AF' : '#808080', disabled: !allowMaternityForms || !questionnairesEnabled } - }, - cache: criteriaCache + } }) ) } catch (error) { @@ -182,7 +184,17 @@ const Requeteur = () => { return true } - // Initial useEffect + // Initial useEffects + + useEffect(() => { + ;(async () => { + if (!valueSets.loading && !valueSets.loaded) { + console.log('fetching valuesets') + await dispatch(initValueSets(criteriaList())).unwrap() + } + setValueSetsLoading(false) + })() + }, [dispatch, valueSets]) useEffect(() => { _fetchRequest() @@ -198,6 +210,7 @@ const Requeteur = () => { if ( loading || + valueSetsLoading || criteriaLoading != 0 || requestLoading != 0 || (!!requestIdFromUrl && requestId !== requestIdFromUrl) diff --git a/src/components/Filters/DatesRangeFilter/index.tsx b/src/components/Filters/DatesRangeFilter/index.tsx index 5038500ce..634fe213a 100644 --- a/src/components/Filters/DatesRangeFilter/index.tsx +++ b/src/components/Filters/DatesRangeFilter/index.tsx @@ -1,4 +1,5 @@ import CalendarRange from 'components/ui/Inputs/CalendarRange' +import { BlockWrapper } from 'components/ui/Layout' import { FormContext } from 'components/ui/Modal' import React, { useContext, useEffect, useState } from 'react' import { DurationRangeType } from 'types/searchCriterias' @@ -27,16 +28,18 @@ const DatesRangeFilter = ({ names, values, disabled }: DatesRangeFilterProps) => }, [endDate]) return ( - { - setStartDate(value[0]) - setEndDate(value[1]) - }} - onError={onError} - /> + + { + setStartDate(value[0]) + setEndDate(value[1]) + }} + onError={onError} + /> + ) } diff --git a/src/components/Filters/DocStatusFilter/index.tsx b/src/components/Filters/DocStatusFilter/index.tsx index 40de56190..2786b2f72 100644 --- a/src/components/Filters/DocStatusFilter/index.tsx +++ b/src/components/Filters/DocStatusFilter/index.tsx @@ -16,7 +16,7 @@ const DocStatusFilter = ({ name, value, docStatusesList, disabled = false }: Doc useEffect(() => { if (context?.updateFormData) context.updateFormData(name, docStatuses) - }, [docStatuses]) + }, [docStatuses, context, name]) return ( diff --git a/src/components/ui/CriteriaLayout/index.tsx b/src/components/ui/CriteriaLayout/index.tsx index dedd59fc0..e2f8c1ce7 100644 --- a/src/components/ui/CriteriaLayout/index.tsx +++ b/src/components/ui/CriteriaLayout/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { ReactNode } from 'react' import useStyles from './styles' import { Alert, Button, Divider, FormLabel, Grid, IconButton, Switch, TextField, Typography } from '@mui/material' import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace' @@ -14,9 +14,9 @@ type CriteriaLayoutProps = { onChangeTitle: (title: string) => void isInclusive: boolean onChangeIsInclusive: (isInclusive: boolean) => void - infoAlert?: string[] - warningAlert?: string[] - errorAlert?: string[] + infoAlert?: ReactNode[] + warningAlert?: ReactNode[] + errorAlert?: ReactNode[] } const CriteriaLayout: React.FC> = ({ @@ -85,7 +85,7 @@ const CriteriaLayout: React.FC> = ({ )} - Critère {criteriaLabel} + {criteriaLabel} void + onChange: (newDuration: DurationRangeType, includeNullValues: boolean) => void onError: (isError: boolean) => void includeNullValues?: boolean onChangeIncludeNullValues?: (includeNullValues: boolean) => void @@ -44,7 +44,7 @@ const CalendarRange = ({ setError({ isError: true, errorMessage: 'La date maximale doit être supérieure à la date minimale.' }) onError(true) } else { - onChange([startDate, endDate]) + onChange([startDate, endDate], isNullValuesChecked) if (onChangeIncludeNullValues) { onChangeIncludeNullValues(isNullValuesChecked) } @@ -52,7 +52,7 @@ const CalendarRange = ({ }, [startDate, endDate, isNullValuesChecked]) return ( - + <> {isString(label) ? ( {label} : @@ -97,7 +97,7 @@ const CalendarRange = ({ {error.errorMessage} )} - + ) } diff --git a/src/components/ui/Inputs/UidTextfield/index.tsx b/src/components/ui/Inputs/CheckedTextfield/index.tsx similarity index 51% rename from src/components/ui/Inputs/UidTextfield/index.tsx rename to src/components/ui/Inputs/CheckedTextfield/index.tsx index 75b9174bd..8489b290e 100644 --- a/src/components/ui/Inputs/UidTextfield/index.tsx +++ b/src/components/ui/Inputs/CheckedTextfield/index.tsx @@ -3,35 +3,64 @@ import { Grid, InputAdornment, TextField, Typography } from '@mui/material' import WarningIcon from '@mui/icons-material/Warning' import { ErrorWrapper } from 'components/ui/Searchbar/styles' -type UidTextfieldProps = { +type CheckedTextfieldProps = { value: string + regex: string + errorMessage?: string + placeholder?: string + multiline?: boolean + inverseCheck?: boolean + extractValidValues?: boolean onChange: (value: string) => void onError: (isError: boolean) => void } -const UidTextfield = ({ value, onChange, onError }: UidTextfieldProps) => { +const CheckedTextfield = ({ + value, + onChange, + onError, + errorMessage, + regex, + placeholder, + inverseCheck = false, + extractValidValues = false, + multiline = false +}: CheckedTextfieldProps) => { const [error, setError] = useState(false) + const [bufferValue, setBufferValue] = useState(value) useEffect(() => { - const regex = /[^0-9.,]/ //matches everything that isn't a number, a comma or a point + const regexp = new RegExp(regex, 'gm') - if (value.match(regex)) { - setError(true) - onError(true) - } else { + if (!bufferValue || !!bufferValue.match(regexp) === !inverseCheck) { setError(false) onError(false) + if (extractValidValues && !inverseCheck) { + const matches = bufferValue.matchAll(regexp) + const extractedValues = [] + for (const match of matches) { + if (match) { + extractedValues.push(match[1]) + } + } + onChange(extractedValues.join(',')) + } else { + onChange(bufferValue) + } + } else { + setError(true) + onError(true) } - }, [value]) + }, [bufferValue]) return ( <> onChange(event.target.value)} - multiline + value={bufferValue} + multiline={multiline} + onChange={(event) => setBufferValue(event.target.value)} minRows={4} style={{ width: '100%' }} error={error} @@ -49,7 +78,7 @@ const UidTextfield = ({ value, onChange, onError }: UidTextfieldProps) => { Des caractères non autorisés ont été détectés dans votre recherche. - Seuls les chiffres, points, ou les virgules sont autorisés. + {errorMessage && {errorMessage}} )} @@ -57,4 +86,4 @@ const UidTextfield = ({ value, onChange, onError }: UidTextfieldProps) => { ) } -export default UidTextfield +export default CheckedTextfield diff --git a/src/components/ui/Inputs/Occurences/index.tsx b/src/components/ui/Inputs/Occurences/index.tsx index dc1dc8dec..f5000e44d 100644 --- a/src/components/ui/Inputs/Occurences/index.tsx +++ b/src/components/ui/Inputs/Occurences/index.tsx @@ -1,93 +1,151 @@ import React, { useEffect, useState } from 'react' -import { MenuItem, Select, SelectChangeEvent, TextField } from '@mui/material' +import { Grid, MenuItem, Select, SelectChangeEvent, TextField } from '@mui/material' import { Comparators } from 'types/requestCriterias' -import { OccurenceInputWrapper } from './styles' import { CriteriaLabel } from 'components/ui/CriteriaLabel' type OccurenceInputProps = { value: number comparator: Comparators - onchange: (value: number, comparator: Comparators) => void + maxValue?: number + onchange: (value: number, comparator: Comparators, upperRangeValue?: number) => void enableNegativeValues?: boolean + floatValues?: boolean label?: string withHierarchyInfo?: boolean + allowBetween?: boolean + disabled?: boolean } const OccurenceInput = ({ value, comparator, + maxValue, onchange, + floatValues = false, enableNegativeValues = false, label, - withHierarchyInfo = false + withHierarchyInfo = false, + allowBetween = false, + disabled = false }: OccurenceInputProps) => { - const [occurrenceValue, setOccurrenceValue] = useState(value) + const [occurrenceValue, setOccurrenceValue] = useState(value) + const [upperRangeValue, setUpperRangeValue] = useState(maxValue) const [comparatorValue, setComparatorValue] = useState(comparator) + const [error, setError] = useState() useEffect(() => { - if (!enableNegativeValues && comparatorValue === Comparators.LESS && occurrenceValue === 0) { - setOccurrenceValue(1) - onchange(1, comparatorValue) + if (upperRangeValue !== undefined && occurrenceValue > upperRangeValue) { + setError('INCOHERENT_VALUE_ERROR') + } else if (comparatorValue === Comparators.BETWEEN && upperRangeValue === undefined) { + setError('MISSING_VALUE_ERROR') + } else { + setError(undefined) + onchange(occurrenceValue, comparatorValue, upperRangeValue) } - }, [comparatorValue, occurrenceValue, enableNegativeValues, onchange]) + }, [comparatorValue, occurrenceValue, upperRangeValue]) - const handleOccurrenceChange = (e: React.ChangeEvent) => { + const checkedValue = (e: React.ChangeEvent) => { const newValue = e.target.value === '' ? '0' : e.target.value - if (newValue.match(/^\d+$/) || (enableNegativeValues && newValue === '0' && comparatorValue === Comparators.LESS)) { - const numericValue = parseInt(newValue, 10) - setOccurrenceValue(numericValue) - onchange(numericValue, comparatorValue) + if (floatValues && newValue.match(enableNegativeValues ? /^-?\d*\.?\d*$/ : /^\d*\.?\d*$/)) { + return parseFloat(newValue) + } else if ( + newValue.match(/^\d+$/) || + (enableNegativeValues && newValue === '0' && comparatorValue === Comparators.LESS) + ) { + return parseInt(newValue, 10) + } + return undefined + } + + const handleValueChange = (e: React.ChangeEvent, setter: (value: number) => void) => { + const newValue = checkedValue(e) + if (newValue !== undefined) { + setter(newValue) } } + const handleMinValueChange = (e: React.ChangeEvent) => { + handleValueChange(e, setOccurrenceValue) + } + + const handleMaxValueChange = (e: React.ChangeEvent) => { + handleValueChange(e, setUpperRangeValue) + } + const handleComparatorChange = (event: SelectChangeEvent) => { const newComparator = event.target.value as Comparators setComparatorValue(newComparator) if (!enableNegativeValues && newComparator === Comparators.LESS && occurrenceValue === 0) { setOccurrenceValue(1) - onchange(1, newComparator) - } else { - onchange(occurrenceValue, newComparator) } } return ( <> - - Si vous choisissez un chapitre, le nombre d'occurrences ne s'applique pas sur un unique élément de ce - chapitre, mais sur l'ensemble des éléments de ce chapitre.
Exemple: Nombre d'occurrences ≥ 3 sur - un chapitre signifie que l'on inclus les patients qui ont eu au moins 3 éléments de ce chapitre, distincts - ou non. - - ) - } - /> - + {label && ( + + Si vous choisissez un chapitre, le nombre d'occurrences ne s'applique pas sur un unique élément de ce + chapitre, mais sur l'ensemble des éléments de ce chapitre.
Exemple: Nombre d'occurrences ≥ 3 + sur un chapitre signifie que l'on inclus les patients qui ont eu au moins 3 éléments de ce chapitre, + distincts ou non. + + ) + } + /> + )} + -
+ {comparatorValue === Comparators.BETWEEN && ( + + )} +
) } diff --git a/src/components/ui/Inputs/OccurencesWithFloats/index.tsx b/src/components/ui/Inputs/OccurencesWithFloats/index.tsx deleted file mode 100644 index 2672c83bb..000000000 --- a/src/components/ui/Inputs/OccurencesWithFloats/index.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { MenuItem, Select, SelectChangeEvent, TextField } from '@mui/material' -import { Comparators } from 'types/requestCriterias' -import { OccurenceInputWrapper } from '../Occurences/styles' - -type NumericConditionInputProps = { - value: number - comparator: Comparators - onchange: (value: number, comparator: Comparators) => void - enableNegativeValues?: boolean -} - -const NumericConditionInput = ({ - value, - comparator, - onchange, - enableNegativeValues = false -}: NumericConditionInputProps) => { - const [occurrenceValue, setOccurrenceValue] = useState(value) - const [comparatorValue, setComparatorValue] = useState(comparator) - - useEffect(() => { - if ( - !enableNegativeValues && - comparatorValue === Comparators.LESS && - (occurrenceValue === 0 || occurrenceValue === '0') - ) { - setOccurrenceValue(1) - onchange(1, comparatorValue) - } - }, [comparatorValue, occurrenceValue, enableNegativeValues, onchange]) - - const handleOccurrenceChange = (e: React.ChangeEvent) => { - const newValue = e.target.value - - if (newValue === '' || newValue.match(enableNegativeValues ? /^-?\d*\.?\d*$/ : /^\d*\.?\d*$/)) { - setOccurrenceValue(newValue) - if (newValue !== '' && newValue !== '-' && newValue !== '.') { - onchange(parseFloat(newValue), comparatorValue) - } else { - setOccurrenceValue(newValue) - } - } - } - const handleComparatorChange = (event: SelectChangeEvent) => { - const newComparator = event.target.value as Comparators - setComparatorValue(newComparator) - if ( - !enableNegativeValues && - newComparator === Comparators.LESS && - (occurrenceValue === 0 || occurrenceValue === '0') - ) { - setOccurrenceValue(1) - onchange(1, newComparator) - } else { - onchange(typeof occurrenceValue === 'string' ? parseFloat(occurrenceValue) : occurrenceValue, newComparator) - } - } - - return ( - - - - - - ) -} - -export default NumericConditionInput diff --git a/src/config.tsx b/src/config.tsx index 88096e7e4..9f77d88d7 100644 --- a/src/config.tsx +++ b/src/config.tsx @@ -2,9 +2,13 @@ import React, { createContext, ReactNode } from 'react' import { Root } from 'react-dom/client' import * as R from 'ramda' import { CONFIG_URL } from 'constants.js' +import { DocumentAttachmentMethod, DocumentAttachmentMethodLabel, LabelObject } from 'types/searchCriterias' +import { birthStatusData, booleanFieldsData, booleanOpenChoiceFieldsData, vmeData } from 'data/questionnaire_data' type ValueSetConfig = { url: string + title?: string + data?: LabelObject[] } type AppConfig = { @@ -63,6 +67,7 @@ type AppConfig = { enabled: boolean valueSets: { imagingModalities: ValueSetConfig + documentAttachementMethod: ValueSetConfig } extensions: { imagingStudyUidUrl: string @@ -94,6 +99,10 @@ type AppConfig = { presentationAtDelivery: ValueSetConfig risksOrComplicationsOfPregnancy: ValueSetConfig risksRelatedToObstetricHistory: ValueSetConfig + booleanOpenChoiceFields: ValueSetConfig + booleanFields: ValueSetConfig + vme: ValueSetConfig + birthStatus: ValueSetConfig } } locationMap: { @@ -209,10 +218,10 @@ let config: AppConfig = { enabled: true, valueSets: { medicationAdministrations: { url: '' }, - medicationAtc: { url: '' }, + medicationAtc: { url: '', title: 'ATC' }, medicationAtcOrbis: { url: '' }, medicationPrescriptionTypes: { url: '' }, - medicationUcd: { url: '' } + medicationUcd: { url: '', title: 'UCD' } } }, condition: { @@ -240,7 +249,24 @@ let config: AppConfig = { imaging: { enabled: true, valueSets: { - imagingModalities: { url: '' } + imagingModalities: { url: '' }, + documentAttachementMethod: { + url: 'documentAttachementMethod', + data: [ + { + id: DocumentAttachmentMethod.NONE, + label: DocumentAttachmentMethodLabel.NONE + }, + { + id: DocumentAttachmentMethod.ACCESS_NUMBER, + label: DocumentAttachmentMethodLabel.ACCESS_NUMBER + }, + { + id: DocumentAttachmentMethod.INFERENCE_TEMPOREL, + label: DocumentAttachmentMethodLabel.INFERENCE_TEMPOREL + } + ] + } }, extensions: { imagingStudyUidUrl: '' @@ -270,7 +296,23 @@ let config: AppConfig = { pregnancyMode: { url: '' }, presentationAtDelivery: { url: '' }, risksOrComplicationsOfPregnancy: { url: '' }, - risksRelatedToObstetricHistory: { url: '' } + risksRelatedToObstetricHistory: { url: '' }, + booleanOpenChoiceFields: { + url: 'booleanOpenChoiceFields', + data: booleanOpenChoiceFieldsData + }, + booleanFields: { + url: 'booleanFields', + data: booleanFieldsData + }, + vme: { + url: 'vme', + data: vmeData + }, + birthStatus: { + url: 'birthStatus', + data: birthStatusData + } } }, locationMap: { diff --git a/src/state/cohortCreation.ts b/src/state/cohortCreation.ts index 337e59294..ad7595f10 100644 --- a/src/state/cohortCreation.ts +++ b/src/state/cohortCreation.ts @@ -485,7 +485,7 @@ const cohortCreationSlice = createSlice({ state.criteriaGroup = state.criteriaGroup.map((item) => ({ ...item, criteriaIds: [] })) state.selectedCriteria = state.selectedCriteria - .filter(({ id }) => id !== criteriaId) + .filter(({ id }) => id !== criteriaId && id !== undefined) .map((selectedCriteria, index) => { // Get the parent of current criteria const parentGroup = criteriaGroupSaved.find((criteriaGroup) => diff --git a/src/state/criteria.ts b/src/state/criteria.ts index bbbae342b..255bc9f1d 100644 --- a/src/state/criteria.ts +++ b/src/state/criteria.ts @@ -2,18 +2,16 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { login, logout } from 'state/me' -import { CriteriaItemDataCache, CriteriaItemType } from 'types' +import { CriteriaItemType } from 'types' export type CriteriaState = { config: { [criteriaKey: string]: Partial } - cache: CriteriaItemDataCache[] } const defaultInitialState = { - config: {}, - cache: [] + config: {} } as CriteriaState const criteriaSlice = createSlice({ diff --git a/src/state/valueSets.ts b/src/state/valueSets.ts index 4d92113a0..e8ede8789 100644 --- a/src/state/valueSets.ts +++ b/src/state/valueSets.ts @@ -1,13 +1,72 @@ -import { createSlice, createEntityAdapter, PayloadAction } from '@reduxjs/toolkit' -import { HierarchyElementWithSystem } from 'types' +import { createSlice, createEntityAdapter, createAsyncThunk, PayloadAction, Dictionary } from '@reduxjs/toolkit' +import { fetchValueSet } from 'services/aphp/callApi' +import { CriteriaItemType, HierarchyElementWithSystem } from 'types' import { logout } from './me' import { LabelObject } from 'types/searchCriterias' +import { getAllCriteriaItems } from 'components/CreationCohort/DataList_Criteria' -type ValueSetOptions = { +export type ValueSetOptions = { id: string options: HierarchyElementWithSystem[] } +export type CodeCache = { [system: string]: LabelObject[] } + +export type ValueSetStore = { entities: Dictionary; cache: CodeCache } + +export const prefetchSmallValueSets = async (criteriaTree: CriteriaItemType[]): Promise> => { + const criteriaList = getAllCriteriaItems(criteriaTree) + + // fetch all unique valueSetIds from the criteriaList + const uniqueValueSetIds = criteriaList + .flatMap((criterion) => { + const criterionValuesets = criterion.formDefinition?.itemSections.flatMap((section) => { + const sectionValuesets = section.items + .map((item) => { + if (item.type === 'autocomplete') { + return { id: item.valueSetId, data: item.valueSetData } + } + return null + }) + .filter((item) => item !== null) + .map((item) => item as { id: string; data?: LabelObject[] }) + return sectionValuesets + }) + + return criterionValuesets || [] + }) + .reduce((acc, item) => { + if (!acc.some((existingItem) => existingItem.id === item.id)) { + acc.push(item) + } + return acc + }, [] as { id: string; data?: LabelObject[] }[]) + + // fetch them + return await Promise.all( + uniqueValueSetIds.map(async (vs) => { + if (vs.data) { + return { id: vs.id, options: vs.data } + } + try { + const options = await fetchValueSet(vs.id, { + joinDisplayWithCode: false, + sortingKey: 'id' + }) + return { id: vs.id, options } + } catch (e) { + console.error(`Error fetching valueSet ${vs.id}`, e) + return { id: vs.id, options: [] } + } + }) + ) +} + +export const initValueSets = createAsyncThunk('valueSets/initValueSets', async (criteriaList: CriteriaItemType[]) => { + const response = await prefetchSmallValueSets(criteriaList) + return response +}) + const valueSetsAdapter = createEntityAdapter() const valueSetsSlice = createSlice({ @@ -16,11 +75,11 @@ const valueSetsSlice = createSlice({ loading: false, error: false, loaded: false, - cache: {} as { [system: string]: LabelObject[] } + cache: {} as CodeCache }), reducers: { addValueSets: valueSetsAdapter.addMany, - updateCache: (state, action: PayloadAction<{ [system: string]: LabelObject[] }>) => { + updateCache: (state, action: PayloadAction) => { return { ...state, cache: action.payload @@ -28,9 +87,23 @@ const valueSetsSlice = createSlice({ } }, extraReducers: (builder) => { - builder.addCase(logout.fulfilled, () => - valueSetsAdapter.getInitialState({ loading: false, error: false, loaded: false, cache: {} }) - ) + builder + .addCase(logout.fulfilled, () => + valueSetsAdapter.getInitialState({ loading: false, error: false, loaded: false, cache: {} }) + ) + .addCase(initValueSets.pending, (state) => { + state.loading = true + state.error = false + }) + .addCase(initValueSets.fulfilled, (state, action) => { + valueSetsAdapter.setAll(state, action.payload) + state.loading = false + state.loaded = true + }) + .addCase(initValueSets.rejected, (state) => { + state.loading = false + state.error = true + }) } }) diff --git a/src/types.ts b/src/types.ts index e7eb62547..d87b3d49e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,7 +24,6 @@ import { AxiosResponse } from 'axios' import { SearchInputError } from 'types/error' import { Comparators, - CriteriaDataKey, CriteriaType, MedicationLabel, PMSIResourceTypes, @@ -35,6 +34,9 @@ import { ExportTableType } from 'components/Dashboard/ExportModal/export_table' import { Hierarchy } from 'types/hierarchy' import { SearchByTypes } from 'types/searchCriterias' import { PMSILabel } from 'types/patient' +import { CriteriaForm } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types' + +export type PartialBy = Omit & Partial> export enum JobStatus { new = 'new', @@ -352,34 +354,29 @@ export type TemporalConstraintsType = { } export type CriteriaDrawerComponentProps = { - parentId: number | null - criteriaData: CriteriaItemDataCache + isOpen?: boolean + parentId?: number | null selectedCriteria: SelectedCriteriaType | null + // TODO remove this when we have the new code search component + onChangeValue?: (key: string, value: any, hierarchy: Hierarchy[]) => void onChangeSelectedCriteria: (newCriteria: SelectedCriteriaType) => void goBack: () => void } -export type CriteriaItemDataCache = { - criteriaType: string - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: { [key in CriteriaDataKey]?: any } -} - export type CriteriaItemType = { id: CriteriaType title: string color: string fontWeight?: string - components: React.FC | null disabled?: boolean - fetch?: { [key in CriteriaDataKey]?: FetchFunctionVariant } subItems?: CriteriaItemType[] + // here we can't know which type of form data we will have, it could really be anything + // eslint-disable-next-line @typescript-eslint/no-explicit-any + formDefinition?: CriteriaForm + // component will always be prefered to formDefinition for rendering the form + component?: React.FC } -type FetchFunctionVariant = - | (() => Promise) - | ((searchValue?: string, noStar?: boolean, signal?: AbortSignal) => Promise[]>) - export type ValueSet = { code: string display: string @@ -737,7 +734,7 @@ export type HierarchyTree = null | { loading?: number } -export type HierarchyElementWithSystem = Hierarchy & { system?: string } +export type HierarchyElementWithSystem = Hierarchy & { system?: string } export type ScopeElement = { id: string diff --git a/src/types/requestCriterias.ts b/src/types/requestCriterias.ts index 09b4f6133..b7a418c17 100644 --- a/src/types/requestCriterias.ts +++ b/src/types/requestCriterias.ts @@ -1,6 +1,15 @@ -import { ScopeElement, SimpleCodeType } from 'types' -import { Hierarchy } from './hierarchy' -import { DocumentAttachmentMethod, DurationRangeType, LabelObject, SearchByTypes } from './searchCriterias' +import { GhmDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/GHMForm' +import { HospitDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/HospitForm' +import { ImagingDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/ImagingForm' +import { ObservationDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/BiologyForm' +import { CcamDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/CCAMForm' +import { DemographicDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DemographicForm' +import { Cim10DataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/Cim10Form' +import { DocumentDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DocumentsForm' +import { EncounterDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/EncounterForm' +import { MedicationDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/MedicationForm' +import { PregnancyDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/PregnancyForm' +import { IPPListDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/IPPForm' export enum QuestionnaireResponseParamsKeys { NAME = 'questionnaire.name', @@ -182,206 +191,36 @@ export enum CriteriaType { HOSPIT = 'Hospit' } -export type CriteriaTypesWithAdvancedInputs = Extract< - CriteriaType, - | CriteriaType.DOCUMENTS - | CriteriaType.CONDITION - | CriteriaType.PROCEDURE - | CriteriaType.CLAIM - | CriteriaType.MEDICATION_REQUEST - | CriteriaType.MEDICATION_ADMINISTRATION - | CriteriaType.OBSERVATION - | CriteriaType.IMAGING -> - -export enum CriteriaTypeLabels { - REQUEST = 'Mes requêtes', - IPP_LIST = "Liste d'IPP", - PATIENT = 'Démographie', - ENCOUNTER = 'Prise en charge', - DOCUMENTS = 'Documents cliniques', - PMSI = 'PMSI', - CONDITION = 'Diagnostics', - PROCEDURE = 'Actes', - CLAIM = 'GHM', - MEDICATION = 'Médicaments', - BIO_MICRO = 'Biologie/Microbiologie', - OBSERVATION = 'Biologie', - MICROBIOLOGIE = 'Microbiologie', - PHYSIOLOGIE = 'Physiologie', - IMAGING = 'Imagerie', - PREGNANCY = 'Fiche grossesse', - HOSPIT = "Fiche d'hospitalisation" -} - -export type CommonCriteriaDataType = { - id: number - error?: boolean - type: CriteriaType - encounterService?: Hierarchy[] - isInclusive?: boolean - title: string -} - -export type WithOccurenceCriteriaDataType = { - occurrence?: number | null - occurrenceComparator?: Comparators | null - startOccurrence: DurationRangeType - endOccurrence?: DurationRangeType -} - -export type WithEncounterDateDataType = { - encounterStartDate: DurationRangeType - includeEncounterStartDateNull?: boolean - encounterEndDate: DurationRangeType - includeEncounterEndDateNull?: boolean -} - -export type WithEncounterStatusDataType = { - encounterStatus: LabelObject[] -} - export type SelectedCriteriaType = + | IPPListDataType + | ObservationDataType | CcamDataType | Cim10DataType | DemographicDataType - | GhmDataType - | EncounterDataType | DocumentDataType - | MedicationDataType - | ObservationDataType - | IPPListDataType + | EncounterDataType + | GhmDataType + | HospitDataType | ImagingDataType + | MedicationDataType | PregnancyDataType - | HospitDataType - -export type SelectedCriteriaTypesWithAdvancedInputs = Exclude< - SelectedCriteriaType, - DemographicDataType | IPPListDataType | PregnancyDataType | HospitDataType | EncounterDataType -> -export type SelectedCriteriaTypesWithOccurrences = Exclude - -export enum CriteriaDataKey { - GENDER = 'gender', - VITALSTATUS = 'status', - PRISE_EN_CHARGE_TYPE = 'priseEnChargeType', - TYPE_DE_SEJOUR = 'typeDeSejour', - FILE_STATUS = 'fileStatus', - ADMISSION_MODE = 'admissionModes', - ADMISSION = 'admission', - ENTRY_MODES = 'entryModes', - EXIT_MODES = 'exitModes', - REASON = 'reason', - DESTINATION = 'destination', - PROVENANCE = 'provenance', - CIM_10_DIAGNOSTIC = 'cim10Diagnostic', - DIAGNOSTIC_TYPES = 'diagnosticTypes', - CCAM_DATA = 'ccamData', - GHM_DATA = 'ghmData', - MEDICATION_DATA = 'medicationData', - PRESCRIPTION_TYPES = 'prescriptionTypes', - ADMINISTRATIONS = 'administrations', - BIOLOGY_DATA = 'biologyData', - MODALITIES = 'modalities', - DOC_TYPES = 'docTypes', - STATUS_DIAGNOSTIC = 'statusDiagnostic', - PREGNANCY_MODE = 'pregnancyMode', - MATERNAL_RISKS = 'maternalRisks', - RISKS_RELATED_TO_OBSTETRIC_HISTORY = 'risksRelatedToObstetricHistory', - RISKS_OR_COMPLICATIONS_OF_PREGNANCY = 'risksOrComplicationsOfPregnancy', - CORTICOTHERAPIE = 'corticotherapie', - PRENATAL_DIAGNOSIS = 'prenatalDiagnosis', - ULTRASOUND_MONITORING = 'ultrasoundMonitoring', - IN_UTERO_TRANSFER = 'inUteroTransfer', - PREGNANCY_MONITORING = 'pregnancyMonitoring', - MATURATION_CORTICOTHERAPIE = 'maturationCorticotherapie', - CHIRURGICAL_GESTURE = 'chirurgicalGesture', - VME = 'vme', - CHILDBIRTH = 'childbirth', - HOSPITALCHILDBIRTHPLACE = 'hospitalChildBirthPlace', - OTHERHOSPITALCHILDBIRTHPLACE = 'otherHospitalChildBirthPlace', - HOMECHILDBIRTHPLACE = 'homeChildBirthPlace', - CHILDBIRTH_MODE = 'childbirthMode', - MATURATION_REASON = 'maturationReason', - MATURATION_MODALITY = 'maturationModality', - IMG_INDICATION = 'imgIndication', - LABOR_OR_CESAREAN_ENTRY = 'laborOrCesareanEntry', - PATHOLOGY_DURING_LABOR = 'pathologyDuringLabor', - OBSTETRICAL_GESTURE_DURING_LABOR = 'obstetricalGestureDuringLabor', - ANALGESIE_TYPE = 'analgesieType', - BIRTH_DELIVERY_WAY = 'birthDeliveryWay', - INSTRUMENT_TYPE = 'instrumentType', - C_SECTION_MODALITY = 'cSectionModality', - PRESENTATION_AT_DELIVERY = 'presentationAtDelivery', - BIRTHSTATUS = 'birthStatus', - POSTPARTUM_HEMORRHAGE = 'postpartumHemorrhage', - CONDITION_PERINEUM = 'conditionPerineum', - EXIT_PLACE_TYPE = 'exitPlaceType', - FEEDING_TYPE = 'feedingType', - COMPLICATION = 'complication', - EXIT_FEEDING_MODE = 'exitFeedingMode', - EXIT_DIAGNOSTIC = 'exitDiagnostic', - DOC_STATUSES = 'docStatuses', - ENCOUNTER_STATUS = 'encounterStatus' -} - -export type CcamDataType = CommonCriteriaDataType & - WithOccurenceCriteriaDataType & - WithEncounterDateDataType & - WithEncounterStatusDataType & { - type: CriteriaType.PROCEDURE - hierarchy: undefined - code: LabelObject[] | null - source: string | null - label: undefined +export type RequeteurCriteriaType = { + // CRITERIA + _type: string + _id: number + name: string + isInclusive: boolean + resourceType: ResourceType + filterFhir: string + occurrence?: { + n?: number | null + operator?: Comparators + timeDelayMin?: number + timeDelayMax?: number } - -export type Cim10DataType = CommonCriteriaDataType & - WithOccurenceCriteriaDataType & - WithEncounterDateDataType & - WithEncounterStatusDataType & { - type: CriteriaType.CONDITION - code: LabelObject[] | null - source: string | null - diagnosticType: LabelObject[] | null - label: undefined - } - -export type DemographicDataType = CommonCriteriaDataType & { - type: CriteriaType.PATIENT - genders: LabelObject[] | null - vitalStatus: LabelObject[] | null - age: DurationRangeType - birthdates: DurationRangeType - deathDates: DurationRangeType } -export type IPPListDataType = CommonCriteriaDataType & { - type: CriteriaType.IPP_LIST - search: string -} - -export type DocumentDataType = CommonCriteriaDataType & - WithOccurenceCriteriaDataType & - WithEncounterDateDataType & - WithEncounterStatusDataType & { - type: CriteriaType.DOCUMENTS - search: string - searchBy: SearchByTypes.TEXT | SearchByTypes.DESCRIPTION - docType: SimpleCodeType[] | null - docStatuses: string[] - } - -export type GhmDataType = CommonCriteriaDataType & - WithOccurenceCriteriaDataType & - WithEncounterDateDataType & - WithEncounterStatusDataType & { - type: CriteriaType.CLAIM - code: LabelObject[] | null - label: undefined - } - export enum Comparators { LESS_OR_EQUAL = '<=', LESS = '<', @@ -391,154 +230,6 @@ export enum Comparators { BETWEEN = '≤x≥' } -export type EncounterDataType = CommonCriteriaDataType & - WithOccurenceCriteriaDataType & - WithEncounterDateDataType & - WithEncounterStatusDataType & { - type: CriteriaType.ENCOUNTER - age: DurationRangeType - duration: DurationRangeType - admissionMode: LabelObject[] | null - entryMode: LabelObject[] | null - exitMode: LabelObject[] | null - priseEnChargeType: LabelObject[] | null - typeDeSejour: LabelObject[] | null - reason: LabelObject[] | null - destination: LabelObject[] | null - provenance: LabelObject[] | null - admission: LabelObject[] | null - } - -export type PregnancyDataType = CommonCriteriaDataType & - WithOccurenceCriteriaDataType & - WithEncounterStatusDataType & { - type: CriteriaType.PREGNANCY - pregnancyStartDate: string | null | undefined - pregnancyEndDate: string | null | undefined - pregnancyMode: LabelObject[] | null - foetus: number - foetusComparator: Comparators - parity: number - parityComparator: Comparators - maternalRisks: LabelObject[] | null - maternalRisksPrecision: string - risksRelatedToObstetricHistory: LabelObject[] | null - risksRelatedToObstetricHistoryPrecision: string - risksOrComplicationsOfPregnancy: LabelObject[] | null - risksOrComplicationsOfPregnancyPrecision: string - corticotherapie: LabelObject[] | null - prenatalDiagnosis: LabelObject[] | null - ultrasoundMonitoring: LabelObject[] | null - } - -export type HospitDataType = CommonCriteriaDataType & - WithOccurenceCriteriaDataType & - WithEncounterStatusDataType & { - type: CriteriaType.HOSPIT - hospitReason: string - inUteroTransfer: LabelObject[] | null - pregnancyMonitoring: LabelObject[] | null - vme: LabelObject[] | null - maturationCorticotherapie: LabelObject[] | null - chirurgicalGesture: LabelObject[] | null - childbirth: LabelObject[] | null - hospitalChildBirthPlace: LabelObject[] | null - otherHospitalChildBirthPlace: LabelObject[] | null - homeChildBirthPlace: LabelObject[] | null - childbirthMode: LabelObject[] | null - maturationReason: LabelObject[] | null - maturationModality: LabelObject[] | null - imgIndication: LabelObject[] | null - laborOrCesareanEntry: LabelObject[] | null - pathologyDuringLabor: LabelObject[] | null - obstetricalGestureDuringLabor: LabelObject[] | null - analgesieType: LabelObject[] | null - birthDeliveryStartDate: string | null | undefined - birthDeliveryEndDate: string | null | undefined - birthDeliveryWeeks: number - birthDeliveryWeeksComparator: Comparators - birthDeliveryDays: number - birthDeliveryDaysComparator: Comparators - birthDeliveryWay: LabelObject[] | null - instrumentType: LabelObject[] | null - cSectionModality: LabelObject[] | null - presentationAtDelivery: LabelObject[] | null - birthMensurationsGrams: number - birthMensurationsGramsComparator: Comparators - birthMensurationsPercentil: number - birthMensurationsPercentilComparator: Comparators - apgar1: number - apgar1Comparator: Comparators - apgar3: number - apgar3Comparator: Comparators - apgar5: number - apgar5Comparator: Comparators - apgar10: number - apgar10Comparator: Comparators - arterialPhCord: number - arterialPhCordComparator: Comparators - arterialCordLactates: number - arterialCordLactatesComparator: Comparators - birthStatus: LabelObject[] | null - postpartumHemorrhage: LabelObject[] | null - conditionPerineum: LabelObject[] | null - exitPlaceType: LabelObject[] | null - feedingType: LabelObject[] | null - complication: LabelObject[] | null - exitFeedingMode: LabelObject[] | null - exitDiagnostic: LabelObject[] | null - } - -export type MedicationDataType = CommonCriteriaDataType & - WithOccurenceCriteriaDataType & - WithEncounterDateDataType & - WithEncounterStatusDataType & { - code: LabelObject[] | null - administration: LabelObject[] | null - } & ( - | { - type: CriteriaType.MEDICATION_REQUEST - prescriptionType: LabelObject[] | null - } - | { type: CriteriaType.MEDICATION_ADMINISTRATION } - ) - -export type ObservationDataType = CommonCriteriaDataType & - WithOccurenceCriteriaDataType & - WithEncounterDateDataType & - WithEncounterStatusDataType & { - type: CriteriaType.OBSERVATION - code: LabelObject[] | null - isLeaf: boolean - searchByValue: [number | null, number | null] - valueComparator: Comparators - } - -export type ImagingDataType = CommonCriteriaDataType & - WithOccurenceCriteriaDataType & - WithEncounterDateDataType & - WithEncounterStatusDataType & { - type: CriteriaType.IMAGING - studyStartDate: string | null - studyEndDate: string | null - studyModalities: LabelObject[] | null - studyDescription: string - studyProcedure: string - numberOfSeries: number - seriesComparator: Comparators - numberOfIns: number - instancesComparator: Comparators - withDocument: DocumentAttachmentMethod - daysOfDelay: string | null - studyUid: string - seriesStartDate: string | null - seriesEndDate: string | null - seriesDescription: string - seriesProtocol: string - seriesModalities: LabelObject[] | null - seriesUid: string - } - export enum VitalStatusLabel { ALIVE = 'Vivant(e)', DECEASED = 'Décédé(e)', diff --git a/src/types/searchCriterias.ts b/src/types/searchCriterias.ts index 0eb962768..4dd5f6dde 100644 --- a/src/types/searchCriterias.ts +++ b/src/types/searchCriterias.ts @@ -226,11 +226,14 @@ export enum DocumentAttachmentMethodLabel { } export type SearchBy = SearchByTypes +// TODO replace this by NewDurationRangeType, this will need the refacto of filters (with a more generic CriteriaForm) export type DurationRangeType = [string | null | undefined, string | null | undefined] export type LabelObject = { id: string label: string system?: string + isLeaf?: boolean + type?: string } export interface OrderBy { orderBy: Order diff --git a/src/utils/cohortCreation.ts b/src/utils/cohortCreation.ts index b95549737..12dc70586 100644 --- a/src/utils/cohortCreation.ts +++ b/src/utils/cohortCreation.ts @@ -1,106 +1,29 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import services from 'services/aphp' -import { - CriteriaGroup, - TemporalConstraintsType, - CriteriaItemType, - CriteriaItemDataCache, - BiologyStatus, - CriteriaGroupType, - ScopeElement -} from 'types' +import { CriteriaGroup, TemporalConstraintsType, CriteriaItemType, CriteriaGroupType, ScopeElement } from 'types' -import { - DocumentAttachmentMethod, - DocumentStatuses, - FormNames, - FilterByDocumentStatus, - LabelObject, - SearchByTypes, - DurationRangeType -} from 'types/searchCriterias' +import { LabelObject } from 'types/searchCriterias' import { Comparators, CriteriaType, SelectedCriteriaType, - CriteriaDataKey, - DemographicDataType, - CommonCriteriaDataType, ResourceType, - EncounterDataType, - DocumentDataType, - Cim10DataType, - PregnancyDataType, - ImagingDataType, - IPPListDataType, - ObservationDataType, - MedicationDataType, - GhmDataType, - CcamDataType, - HospitDataType, - SelectedCriteriaTypesWithOccurrences, - AdministrationParamsKeys, - ClaimParamsKeys, - ConditionParamsKeys, - DocumentsParamsKeys, - EncounterParamsKeys, - ImagingParamsKeys, - IppParamsKeys, - ObservationParamsKeys, - PatientsParamsKeys, - PrescriptionParamsKeys, - ProcedureParamsKeys, - QuestionnaireResponseParamsKeys + RequeteurCriteriaType } from 'types/requestCriterias' -import { parseOccurence } from './valueComparator' -import { parseDocumentAttachment } from './documentAttachment' import { - buildComparatorFilter, - buildDateFilter, - buildDurationFilter, - buildLabelObjectFilter, - buildObservationValueFilter, - buildSearchFilter, - buildSimpleFilter, - buildWithDocumentFilter, - unbuildOccurrence, - unbuildDateFilter, - unbuildDurationFilter, - unbuildDocTypesFilter, - unbuildEncounterServiceCriterias, - unbuildLabelObjectFilter, - unbuildObservationValueFilter, - unbuildSearchFilter, - buildEncounterServiceFilter, - filtersBuilders, - unbuildDocStatusesFilter, - questionnaireFiltersBuilders, - unbuildQuestionnaireFilters, - findQuestionnaireName, - buildEncounterDateFilter, - getCriterionDateFilterName, - unbuildEncounterDatesFilters -} from './mappers' -import { pregnancyForm } from 'data/pregnancyData' -import { hospitForm } from 'data/hospitData' + constructFhirFilterForType, + unbuildCriteriaDataFromDefinition +} from '../components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers' import { editAllCriteria, editAllCriteriaGroup, pseudonimizeCriteria, buildCohortCreation } from 'state/cohortCreation' import { AppDispatch } from 'state' import { Hierarchy } from 'types/hierarchy' -import { getConfig } from 'config' +import { fetchValueSet } from 'services/aphp/callApi' +import { CodeCache } from 'state/valueSets' +import { NewDurationRangeType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types' +import { isString } from 'lodash' +import criteriaList, { getAllCriteriaItems } from 'components/CreationCohort/DataList_Criteria' -const REQUETEUR_VERSION = 'v1.6.0' - -const DEFAULT_CRITERIA_ERROR: SelectedCriteriaType = { - id: 0, - isInclusive: false, - type: CriteriaType.PATIENT, - title: '', - genders: [], - vitalStatus: [], - birthdates: [null, null], - deathDates: [null, null], - age: [null, null] -} +const REQUETEUR_VERSION = 'v1.5.1' const DEFAULT_GROUP_ERROR: CriteriaGroup = { id: 0, @@ -109,22 +32,6 @@ const DEFAULT_GROUP_ERROR: CriteriaGroup = { criteriaIds: [] } -export type RequeteurCriteriaType = { - // CRITERIA - _type: string - _id: number - name: string - isInclusive: boolean - resourceType: ResourceType - filterFhir: string - occurrence?: { - n?: number | null - operator?: Comparators - timeDelayMin?: number - timeDelayMax?: number - } -} - type RequeteurGroupType = | { // GROUP (andGroup | orGroup) @@ -167,16 +74,14 @@ export const checkNominativeCriteria = (selectedCriteria: SelectedCriteriaType[] const isPatientWithDates = selectedCriteria.some( (criterion) => criterion.type === CriteriaType.PATIENT && - (criterion.birthdates[0] !== null || - criterion.birthdates[1] !== null || - criterion.deathDates[0] !== null || - criterion.deathDates[1] !== null) + ((criterion.birthdates !== null && (criterion.birthdates.start !== null || criterion.birthdates.end !== null)) || + (criterion.deathDates !== null && (criterion.deathDates.start !== null || criterion.deathDates.end !== null))) ) const isEncounterWithAgesInDays = selectedCriteria.some( (criterion) => (criterion.type === CriteriaType.ENCOUNTER || criterion.type === CriteriaType.PATIENT) && - (criterion.age?.[0]?.match(regex) || criterion.age?.[1]?.match(regex)) + (criterion.age?.start?.match(regex) || criterion.age?.end?.match(regex)) ) const isSensitiveCriteria = selectedCriteria.some( @@ -195,7 +100,10 @@ export const cleanNominativeCriterias = ( dispatch: AppDispatch, selectedPopulation?: ScopeElement[] ) => { - const cleanDurationRange = (value: DurationRangeType) => { + const cleanDurationRange = (value: NewDurationRangeType | null) => { + if (value === null) { + return null + } const regex = /^[^/]*\// // matches everything before the first '/' const cleanValue = (value: string | null | undefined) => { @@ -204,7 +112,7 @@ export const cleanNominativeCriterias = ( } else return null } - return [cleanValue(value[0]), cleanValue(value[1])] as DurationRangeType + return { start: cleanValue(value.start), end: cleanValue(value.end), includeNull: value.includeNull } } const cleanedSelectedCriteria = selectedCriteria @@ -219,8 +127,8 @@ export const cleanNominativeCriterias = ( case CriteriaType.PATIENT: { return { ...criterion, - birthdates: [null, null] as DurationRangeType, - deathDates: [null, null] as DurationRangeType, + birthdates: null, + deathDates: null, age: cleanDurationRange(criterion.age) } } @@ -267,487 +175,21 @@ export const cleanNominativeCriterias = ( } } -export const buildPatientFilter = (criterion: DemographicDataType, deidentified: boolean): string[] => { - const isDeidentified = deidentified ? PatientsParamsKeys.DATE_DEIDENTIFIED : PatientsParamsKeys.DATE_IDENTIFIED - return [ - 'active=true', - filtersBuilders(PatientsParamsKeys.GENDERS, buildLabelObjectFilter(criterion.genders)), - filtersBuilders(PatientsParamsKeys.VITAL_STATUS, buildLabelObjectFilter(criterion.vitalStatus)), - filtersBuilders(PatientsParamsKeys.BIRTHDATE, buildDateFilter(criterion.birthdates[1], 'le')), - filtersBuilders(PatientsParamsKeys.BIRTHDATE, buildDateFilter(criterion.birthdates[0], 'ge')), - filtersBuilders(PatientsParamsKeys.DEATHDATE, buildDateFilter(criterion.deathDates[0], 'ge')), - filtersBuilders(PatientsParamsKeys.DEATHDATE, buildDateFilter(criterion.deathDates[1], 'le')), - criterion.birthdates[0] === null && criterion.birthdates[1] === null - ? buildDurationFilter(criterion.age[0], isDeidentified, 'ge', deidentified) - : '', - criterion.birthdates[0] === null && criterion.birthdates[1] === null - ? buildDurationFilter(criterion.age[1], isDeidentified, 'le', deidentified) - : '' - ] -} - -export const buildEncounterFilter = (criterion: EncounterDataType, deidentified: boolean): string[] => { - const isMinBirthdateDeidentified = deidentified - ? EncounterParamsKeys.MIN_BIRTHDATE_MONTH - : EncounterParamsKeys.MIN_BIRTHDATE_DAY - return [ - 'subject.active=true', - filtersBuilders(EncounterParamsKeys.ADMISSIONMODE, buildLabelObjectFilter(criterion.admissionMode)), - filtersBuilders(EncounterParamsKeys.ENTRYMODE, buildLabelObjectFilter(criterion.entryMode)), - filtersBuilders(EncounterParamsKeys.EXITMODE, buildLabelObjectFilter(criterion.exitMode)), - filtersBuilders(EncounterParamsKeys.PRISENCHARGETYPE, buildLabelObjectFilter(criterion.priseEnChargeType)), - filtersBuilders(EncounterParamsKeys.TYPEDESEJOUR, buildLabelObjectFilter(criterion.typeDeSejour)), - filtersBuilders(EncounterParamsKeys.DESTINATION, buildLabelObjectFilter(criterion.destination)), - filtersBuilders(EncounterParamsKeys.PROVENANCE, buildLabelObjectFilter(criterion.provenance)), - filtersBuilders(EncounterParamsKeys.ADMISSION, buildLabelObjectFilter(criterion.admission)), - filtersBuilders(EncounterParamsKeys.REASON, buildLabelObjectFilter(criterion.reason)), - filtersBuilders(EncounterParamsKeys.SERVICE_PROVIDER, buildEncounterServiceFilter(criterion.encounterService)), - filtersBuilders(EncounterParamsKeys.STATUS, buildLabelObjectFilter(criterion.encounterStatus)), - criterion.duration[0] !== null - ? buildDurationFilter(criterion?.duration?.[0], EncounterParamsKeys.DURATION, 'ge') - : '', - criterion.duration[1] !== null - ? buildDurationFilter(criterion?.duration?.[1], EncounterParamsKeys.DURATION, 'le') - : '', - criterion.age[0] !== null - ? buildDurationFilter(criterion?.age[0], isMinBirthdateDeidentified, 'ge', deidentified) - : '', - criterion.age[1] !== null - ? buildDurationFilter(criterion?.age[1], isMinBirthdateDeidentified, 'le', deidentified) - : '', - buildEncounterDateFilter( - criterion.type, - criterion.includeEncounterStartDateNull, - criterion.encounterStartDate, - true - ), - buildEncounterDateFilter(criterion.type, criterion.includeEncounterEndDateNull, criterion.encounterEndDate) - ] -} - -export const buildDocumentFilter = (criterion: DocumentDataType): string[] => { - const docStatusCodeSystem = getConfig().core.codeSystems.docStatus - const joinDocStatuses = (docStatuses: string[]): string => { - const filterDocStatuses: string[] = [] - for (const _status of docStatuses) { - const status = - _status === FilterByDocumentStatus.VALIDATED ? DocumentStatuses.FINAL : DocumentStatuses.PRELIMINARY - filterDocStatuses.push(`${docStatusCodeSystem}|${status}`) - } - return filterDocStatuses.join(',') - } - - return [ - `type:not=doc-impor&contenttype=text/plain&subject.active=true`, - filtersBuilders(DocumentsParamsKeys.EXECUTIVE_UNITS, buildEncounterServiceFilter(criterion.encounterService)), - filtersBuilders( - criterion.searchBy === SearchByTypes.TEXT ? SearchByTypes.TEXT : SearchByTypes.DESCRIPTION, - buildSearchFilter(criterion.search) - ), - filtersBuilders(DocumentsParamsKeys.DOC_STATUSES, joinDocStatuses(criterion.docStatuses)), - filtersBuilders( - DocumentsParamsKeys.DOC_TYPES, - buildLabelObjectFilter( - criterion.docType?.map((docType) => { - return { - id: docType.code - } as LabelObject - }) - ) - ), - filtersBuilders(DocumentsParamsKeys.ENCOUNTER_STATUS, buildLabelObjectFilter(criterion.encounterStatus)), - filtersBuilders(DocumentsParamsKeys.DATE, buildDateFilter(criterion.startOccurrence[0], 'ge')), - filtersBuilders(DocumentsParamsKeys.DATE, buildDateFilter(criterion.startOccurrence[1], 'le')), - buildEncounterDateFilter( - criterion.type, - criterion.includeEncounterStartDateNull, - criterion.encounterStartDate, - true - ), - buildEncounterDateFilter(criterion.type, criterion.includeEncounterEndDateNull, criterion.encounterEndDate) - ] -} - -export const buildConditionFilter = (criterion: Cim10DataType): string[] => { - return [ - 'subject.active=true', - filtersBuilders( - ConditionParamsKeys.CODE, - buildLabelObjectFilter(criterion.code, getConfig().features.condition.valueSets.conditionHierarchy.url) - ), - filtersBuilders(ConditionParamsKeys.DIAGNOSTIC_TYPES, buildLabelObjectFilter(criterion.diagnosticType)), - criterion.source ? buildSimpleFilter(criterion.source, ProcedureParamsKeys.SOURCE) : '', - filtersBuilders(ConditionParamsKeys.EXECUTIVE_UNITS, buildEncounterServiceFilter(criterion.encounterService)), - filtersBuilders(ConditionParamsKeys.ENCOUNTER_STATUS, buildLabelObjectFilter(criterion.encounterStatus)), - filtersBuilders(ConditionParamsKeys.DATE, buildDateFilter(criterion.startOccurrence[0], 'ge')), - filtersBuilders(ConditionParamsKeys.DATE, buildDateFilter(criterion.startOccurrence[1], 'le')), - buildEncounterDateFilter( - criterion.type, - criterion.includeEncounterStartDateNull, - criterion.encounterStartDate, - true - ), - buildEncounterDateFilter(criterion.type, criterion.includeEncounterEndDateNull, criterion.encounterEndDate) - ] -} - -export const buildProcedureFilter = (criterion: CcamDataType): string[] => { - return [ - 'subject.active=true', - filtersBuilders( - ProcedureParamsKeys.CODE, - buildLabelObjectFilter(criterion.code, getConfig().features.procedure.valueSets.procedureHierarchy.url) - ), - filtersBuilders(ProcedureParamsKeys.EXECUTIVE_UNITS, buildEncounterServiceFilter(criterion.encounterService)), - filtersBuilders(ProcedureParamsKeys.ENCOUNTER_STATUS, buildLabelObjectFilter(criterion.encounterStatus)), - filtersBuilders(ProcedureParamsKeys.DATE, buildDateFilter(criterion.startOccurrence[0], 'ge')), - filtersBuilders(ProcedureParamsKeys.DATE, buildDateFilter(criterion.startOccurrence[1], 'le')), - criterion.source ? buildSimpleFilter(criterion.source, ProcedureParamsKeys.SOURCE) : '', - buildEncounterDateFilter( - criterion.type, - criterion.includeEncounterStartDateNull, - criterion.encounterStartDate, - true - ), - buildEncounterDateFilter(criterion.type, criterion.includeEncounterEndDateNull, criterion.encounterEndDate) - ] -} - -export const buildClaimFilter = (criterion: GhmDataType): string[] => { - return [ - 'patient.active=true', - filtersBuilders( - ClaimParamsKeys.CODE, - buildLabelObjectFilter(criterion.code, getConfig().features.claim.valueSets.claimHierarchy.url) - ), - filtersBuilders(ClaimParamsKeys.EXECUTIVE_UNITS, buildEncounterServiceFilter(criterion.encounterService)), - filtersBuilders(ClaimParamsKeys.ENCOUNTER_STATUS, buildLabelObjectFilter(criterion.encounterStatus)), - filtersBuilders(ClaimParamsKeys.DATE, buildDateFilter(criterion.startOccurrence[0], 'ge')), - filtersBuilders(ClaimParamsKeys.DATE, buildDateFilter(criterion.startOccurrence[1], 'le')), - buildEncounterDateFilter( - criterion.type, - criterion.includeEncounterStartDateNull, - criterion.encounterStartDate, - true - ), - buildEncounterDateFilter(criterion.type, criterion.includeEncounterEndDateNull, criterion.encounterEndDate) - ] -} - -export const buildMedicationFilter = (criterion: MedicationDataType): string[] => { - return [ - 'subject.active=true', - filtersBuilders( - criterion.type === CriteriaType.MEDICATION_REQUEST - ? PrescriptionParamsKeys.PRESCRIPTION_ROUTES - : AdministrationParamsKeys.ADMINISTRATION_ROUTES, - buildLabelObjectFilter(criterion.administration) - ), - filtersBuilders( - criterion.type === CriteriaType.MEDICATION_REQUEST - ? PrescriptionParamsKeys.EXECUTIVE_UNITS - : AdministrationParamsKeys.EXECUTIVE_UNITS, - buildEncounterServiceFilter(criterion.encounterService) - ), - filtersBuilders( - PrescriptionParamsKeys.CODE, - buildLabelObjectFilter(criterion.code, getConfig().features.medication.valueSets.medicationAtc.url, true) - ), - filtersBuilders( - criterion.type === CriteriaType.MEDICATION_REQUEST - ? PrescriptionParamsKeys.ENCOUNTER_STATUS - : AdministrationParamsKeys.ENCOUNTER_STATUS, - buildLabelObjectFilter(criterion.encounterStatus) - ), - filtersBuilders( - criterion.type === CriteriaType.MEDICATION_REQUEST ? PrescriptionParamsKeys.DATE : AdministrationParamsKeys.DATE, - buildDateFilter(criterion.startOccurrence[0], 'ge') - ), - filtersBuilders( - criterion.type === CriteriaType.MEDICATION_REQUEST ? PrescriptionParamsKeys.DATE : AdministrationParamsKeys.DATE, - buildDateFilter(criterion.startOccurrence[1], 'le') - ), - filtersBuilders(PrescriptionParamsKeys.END_DATE, buildDateFilter(criterion.endOccurrence?.[0], 'ge')), - filtersBuilders(PrescriptionParamsKeys.END_DATE, buildDateFilter(criterion.endOccurrence?.[1], 'le')), - criterion.type === CriteriaType.MEDICATION_REQUEST - ? filtersBuilders(PrescriptionParamsKeys.PRESCRIPTION_TYPES, buildLabelObjectFilter(criterion.prescriptionType)) - : '', - buildEncounterDateFilter( - criterion.type, - criterion.includeEncounterStartDateNull, - criterion.encounterStartDate, - true - ), - buildEncounterDateFilter(criterion.type, criterion.includeEncounterEndDateNull, criterion.encounterEndDate) - ] -} - -export const buildObservationFilter = (criterion: ObservationDataType): string[] => { - return [ - `subject.active=true&${ObservationParamsKeys.VALIDATED_STATUS}=${BiologyStatus.VALIDATED}`, - filtersBuilders( - ObservationParamsKeys.ANABIO_LOINC, - buildLabelObjectFilter(criterion.code, getConfig().features.observation.valueSets.biologyHierarchyAnabio.url) - ), - filtersBuilders(ObservationParamsKeys.EXECUTIVE_UNITS, buildEncounterServiceFilter(criterion.encounterService)), - filtersBuilders(ObservationParamsKeys.ENCOUNTER_STATUS, buildLabelObjectFilter(criterion.encounterStatus)), - filtersBuilders(ObservationParamsKeys.DATE, buildDateFilter(criterion.startOccurrence[0], 'ge')), - filtersBuilders(ObservationParamsKeys.DATE, buildDateFilter(criterion.startOccurrence[1], 'le')), - buildObservationValueFilter(criterion, ObservationParamsKeys.VALUE), - buildEncounterDateFilter( - criterion.type, - criterion.includeEncounterStartDateNull, - criterion.encounterStartDate, - true - ), - buildEncounterDateFilter(criterion.type, criterion.includeEncounterEndDateNull, criterion.encounterEndDate) - ] -} - -export const buildImagingFilter = (criterion: ImagingDataType): string[] => { - return [ - 'patient.active=true', - filtersBuilders(ImagingParamsKeys.DATE, buildDateFilter(criterion.studyStartDate, 'ge')), - filtersBuilders(ImagingParamsKeys.DATE, buildDateFilter(criterion.studyEndDate, 'le')), - filtersBuilders(ImagingParamsKeys.SERIES_DATE, buildDateFilter(criterion.seriesStartDate, 'ge')), - filtersBuilders(ImagingParamsKeys.SERIES_DATE, buildDateFilter(criterion.seriesEndDate, 'le')), - filtersBuilders(ImagingParamsKeys.STUDY_DESCRIPTION, buildSearchFilter(criterion.studyDescription)), - filtersBuilders(ImagingParamsKeys.STUDY_PROCEDURE, buildSearchFilter(criterion.studyProcedure)), - filtersBuilders(ImagingParamsKeys.SERIES_DESCRIPTION, buildSearchFilter(criterion.seriesDescription)), - filtersBuilders(ImagingParamsKeys.SERIES_PROTOCOL, buildSearchFilter(criterion.seriesProtocol)), - filtersBuilders(ImagingParamsKeys.MODALITY, buildLabelObjectFilter(criterion.studyModalities)), - filtersBuilders(ImagingParamsKeys.SERIES_MODALITIES, buildLabelObjectFilter(criterion.seriesModalities)), - filtersBuilders(ImagingParamsKeys.EXECUTIVE_UNITS, buildEncounterServiceFilter(criterion.encounterService)), - filtersBuilders( - ImagingParamsKeys.NB_OF_SERIES, - buildComparatorFilter(criterion.numberOfSeries, criterion.seriesComparator) - ), - filtersBuilders( - ImagingParamsKeys.NB_OF_INS, - buildComparatorFilter(criterion.numberOfIns, criterion.instancesComparator) - ), - filtersBuilders(ImagingParamsKeys.ENCOUNTER_STATUS, buildLabelObjectFilter(criterion.encounterStatus)), - buildWithDocumentFilter(criterion, ImagingParamsKeys.WITH_DOCUMENT), - buildSimpleFilter( - criterion.studyUid, - ImagingParamsKeys.STUDY_UID, - getConfig().features.imaging.extensions.imagingStudyUidUrl - ), - buildSimpleFilter(criterion.seriesUid, ImagingParamsKeys.SERIES_UID), - buildEncounterDateFilter( - criterion.type, - criterion.includeEncounterStartDateNull, - criterion.encounterStartDate, - true - ), - buildEncounterDateFilter(criterion.type, criterion.includeEncounterEndDateNull, criterion.encounterEndDate) - ] -} - -export const buildPregnancyFilter = (criterion: PregnancyDataType): string[] => { - return [ - 'subject.active=true', - `questionnaire.name=${FormNames.PREGNANCY}`, - 'status=in-progress,completed', - filtersBuilders( - QuestionnaireResponseParamsKeys.ENCOUNTER_STATUS, - buildLabelObjectFilter(criterion.encounterStatus) - ), - questionnaireFiltersBuilders( - pregnancyForm.pregnancyStartDate, - buildDateFilter(criterion.pregnancyStartDate, 'ge', true) - ), - questionnaireFiltersBuilders( - pregnancyForm.pregnancyEndDate, - buildDateFilter(criterion.pregnancyEndDate, 'le', true) - ), - questionnaireFiltersBuilders(pregnancyForm.pregnancyMode, buildLabelObjectFilter(criterion.pregnancyMode)), - questionnaireFiltersBuilders( - pregnancyForm.foetus, - buildComparatorFilter(criterion.foetus, criterion.foetusComparator) - ), - questionnaireFiltersBuilders( - pregnancyForm.parity, - buildComparatorFilter(criterion.parity, criterion.parityComparator) - ), - questionnaireFiltersBuilders(pregnancyForm.maternalRisks, buildLabelObjectFilter(criterion.maternalRisks)), - questionnaireFiltersBuilders( - pregnancyForm.maternalRisksPrecision, - buildSearchFilter(criterion.maternalRisksPrecision) - ), - questionnaireFiltersBuilders( - pregnancyForm.risksRelatedToObstetricHistory, - buildLabelObjectFilter(criterion.risksRelatedToObstetricHistory) - ), - questionnaireFiltersBuilders( - pregnancyForm.risksRelatedToObstetricHistoryPrecision, - buildSearchFilter(criterion.risksRelatedToObstetricHistoryPrecision) - ), - questionnaireFiltersBuilders( - pregnancyForm.risksOrComplicationsOfPregnancy, - buildLabelObjectFilter(criterion.risksOrComplicationsOfPregnancy) - ), - questionnaireFiltersBuilders( - pregnancyForm.risksOrComplicationsOfPregnancyPrecision, - buildSearchFilter(criterion.risksOrComplicationsOfPregnancyPrecision) - ), - questionnaireFiltersBuilders(pregnancyForm.corticotherapie, buildLabelObjectFilter(criterion.corticotherapie)), - questionnaireFiltersBuilders(pregnancyForm.prenatalDiagnosis, buildLabelObjectFilter(criterion.prenatalDiagnosis)), - questionnaireFiltersBuilders( - pregnancyForm.ultrasoundMonitoring, - buildLabelObjectFilter(criterion.ultrasoundMonitoring) - ), - questionnaireFiltersBuilders( - { id: QuestionnaireResponseParamsKeys.EXECUTIVE_UNITS, type: 'valueCoding' }, - buildEncounterServiceFilter(criterion.encounterService) - ) - ] -} - -export const buildHospitFilter = (criterion: HospitDataType): string[] => { - return [ - 'subject.active=true', - `questionnaire.name=${FormNames.HOSPIT}`, - 'status=in-progress,completed', - filtersBuilders( - QuestionnaireResponseParamsKeys.ENCOUNTER_STATUS, - buildLabelObjectFilter(criterion.encounterStatus) - ), - questionnaireFiltersBuilders(hospitForm.hospitReason, buildSearchFilter(criterion.hospitReason)), - questionnaireFiltersBuilders(hospitForm.inUteroTransfer, buildLabelObjectFilter(criterion.inUteroTransfer)), - questionnaireFiltersBuilders(hospitForm.pregnancyMonitoring, buildLabelObjectFilter(criterion.pregnancyMonitoring)), - questionnaireFiltersBuilders(hospitForm.vme, buildLabelObjectFilter(criterion.vme)), - questionnaireFiltersBuilders( - hospitForm.maturationCorticotherapie, - buildLabelObjectFilter(criterion.maturationCorticotherapie) - ), - questionnaireFiltersBuilders(hospitForm.chirurgicalGesture, buildLabelObjectFilter(criterion.chirurgicalGesture)), - questionnaireFiltersBuilders(hospitForm.childbirth, buildLabelObjectFilter(criterion.childbirth)), - questionnaireFiltersBuilders( - hospitForm.hospitalChildBirthPlace, - buildLabelObjectFilter(criterion.hospitalChildBirthPlace) - ), - questionnaireFiltersBuilders( - hospitForm.otherHospitalChildBirthPlace, - buildLabelObjectFilter(criterion.otherHospitalChildBirthPlace) - ), - questionnaireFiltersBuilders(hospitForm.homeChildBirthPlace, buildLabelObjectFilter(criterion.homeChildBirthPlace)), - questionnaireFiltersBuilders(hospitForm.childbirthMode, buildLabelObjectFilter(criterion.childbirthMode)), - questionnaireFiltersBuilders(hospitForm.maturationReason, buildLabelObjectFilter(criterion.maturationReason)), - questionnaireFiltersBuilders(hospitForm.maturationModality, buildLabelObjectFilter(criterion.maturationModality)), - questionnaireFiltersBuilders(hospitForm.imgIndication, buildLabelObjectFilter(criterion.imgIndication)), - questionnaireFiltersBuilders( - hospitForm.laborOrCesareanEntry, - buildLabelObjectFilter(criterion.laborOrCesareanEntry) - ), - questionnaireFiltersBuilders( - hospitForm.pathologyDuringLabor, - buildLabelObjectFilter(criterion.pathologyDuringLabor) - ), - questionnaireFiltersBuilders( - hospitForm.obstetricalGestureDuringLabor, - buildLabelObjectFilter(criterion.obstetricalGestureDuringLabor) - ), - questionnaireFiltersBuilders(hospitForm.analgesieType, buildLabelObjectFilter(criterion.analgesieType)), - questionnaireFiltersBuilders( - hospitForm.birthDeliveryStartDate, - buildDateFilter(criterion.birthDeliveryStartDate, 'ge', true) - ), - questionnaireFiltersBuilders( - hospitForm.birthDeliveryEndDate, - buildDateFilter(criterion.birthDeliveryEndDate, 'le', true) - ), - questionnaireFiltersBuilders( - hospitForm.birthDeliveryWeeks, - buildComparatorFilter(criterion.birthDeliveryWeeks, criterion.birthDeliveryWeeksComparator) - ), - questionnaireFiltersBuilders( - hospitForm.birthDeliveryDays, - buildComparatorFilter(criterion.birthDeliveryDays, criterion.birthDeliveryDaysComparator) - ), - questionnaireFiltersBuilders(hospitForm.birthDeliveryWay, buildLabelObjectFilter(criterion.birthDeliveryWay)), - questionnaireFiltersBuilders(hospitForm.instrumentType, buildLabelObjectFilter(criterion.instrumentType)), - questionnaireFiltersBuilders(hospitForm.cSectionModality, buildLabelObjectFilter(criterion.cSectionModality)), - questionnaireFiltersBuilders( - hospitForm.presentationAtDelivery, - buildLabelObjectFilter(criterion.presentationAtDelivery) - ), - questionnaireFiltersBuilders( - hospitForm.birthMensurationsGrams, - buildComparatorFilter(criterion.birthMensurationsGrams, criterion.birthMensurationsGramsComparator) - ), - questionnaireFiltersBuilders( - hospitForm.birthMensurationsPercentil, - buildComparatorFilter(criterion.birthMensurationsPercentil, criterion.birthMensurationsPercentilComparator) - ), - questionnaireFiltersBuilders( - hospitForm.apgar1, - buildComparatorFilter(criterion.apgar1, criterion.apgar1Comparator) - ), - questionnaireFiltersBuilders( - hospitForm.apgar3, - buildComparatorFilter(criterion.apgar3, criterion.apgar3Comparator) - ), - questionnaireFiltersBuilders( - hospitForm.apgar5, - buildComparatorFilter(criterion.apgar5, criterion.apgar5Comparator) - ), - questionnaireFiltersBuilders( - hospitForm.apgar10, - buildComparatorFilter(criterion.apgar10, criterion.apgar10Comparator) - ), - questionnaireFiltersBuilders( - hospitForm.arterialPhCord, - buildComparatorFilter(criterion.arterialPhCord, criterion.arterialPhCordComparator) - ), - questionnaireFiltersBuilders( - hospitForm.arterialCordLactates, - buildComparatorFilter(criterion.arterialCordLactates, criterion.arterialCordLactatesComparator) - ), - questionnaireFiltersBuilders(hospitForm.birthStatus, buildLabelObjectFilter(criterion.birthStatus)), - questionnaireFiltersBuilders( - hospitForm.postpartumHemorrhage, - buildLabelObjectFilter(criterion.postpartumHemorrhage) - ), - questionnaireFiltersBuilders(hospitForm.conditionPerineum, buildLabelObjectFilter(criterion.conditionPerineum)), - questionnaireFiltersBuilders(hospitForm.exitPlaceType, buildLabelObjectFilter(criterion.exitPlaceType)), - questionnaireFiltersBuilders(hospitForm.feedingType, buildLabelObjectFilter(criterion.feedingType)), - questionnaireFiltersBuilders(hospitForm.complication, buildLabelObjectFilter(criterion.complication)), - questionnaireFiltersBuilders(hospitForm.exitFeedingMode, buildLabelObjectFilter(criterion.exitFeedingMode)), - questionnaireFiltersBuilders(hospitForm.exitDiagnostic, buildLabelObjectFilter(criterion.exitDiagnostic)), - questionnaireFiltersBuilders( - { id: QuestionnaireResponseParamsKeys.EXECUTIVE_UNITS, type: 'valueCoding' }, - buildEncounterServiceFilter(criterion.encounterService) - ) - ] -} - -const constructFilterFhir = (criterion: SelectedCriteriaType, deidentified: boolean): string => { - const filterReducer = (accumulator: string, currentValue: string): string => - accumulator ? `${accumulator}&${currentValue}` : currentValue ? currentValue : accumulator - - const filterBuilders: Partial<{ [K in CriteriaType]: (c: SelectedCriteriaType, b: boolean) => string[] }> = { - [CriteriaType.PATIENT]: (c, b) => buildPatientFilter(c as DemographicDataType, b), - [CriteriaType.ENCOUNTER]: (c, b) => buildEncounterFilter(c as EncounterDataType, b), - [CriteriaType.DOCUMENTS]: (c) => buildDocumentFilter(c as DocumentDataType), - [CriteriaType.CONDITION]: (c) => buildConditionFilter(c as Cim10DataType), - [CriteriaType.PROCEDURE]: (c) => buildProcedureFilter(c as CcamDataType), - [CriteriaType.CLAIM]: (c) => buildClaimFilter(c as GhmDataType), - [CriteriaType.MEDICATION_REQUEST]: (c) => buildMedicationFilter(c as MedicationDataType), - [CriteriaType.MEDICATION_ADMINISTRATION]: (c) => buildMedicationFilter(c as MedicationDataType), - [CriteriaType.OBSERVATION]: (c) => buildObservationFilter(c as ObservationDataType), - [CriteriaType.IPP_LIST]: (c) => - ((criterion: IPPListDataType) => [filtersBuilders(IppParamsKeys.IPP_LIST_FHIR, criterion.search)])( - c as IPPListDataType - ), - [CriteriaType.IMAGING]: (c) => buildImagingFilter(c as ImagingDataType), - [CriteriaType.PREGNANCY]: (c) => buildPregnancyFilter(c as PregnancyDataType), - [CriteriaType.HOSPIT]: (c) => buildHospitFilter(c as HospitDataType) +export const constructFhirFilter = ( + criteria: SelectedCriteriaType, + deidentified: boolean, + allcriterias: CriteriaItemType[] +): string => { + const formDefinition = allcriterias.find((crit) => + isString(crit.formDefinition?.buildInfo?.criteriaType) + ? crit.formDefinition?.buildInfo?.criteriaType === criteria.type + : crit.formDefinition?.buildInfo?.criteriaType.includes(criteria.type) + )?.formDefinition + if (!formDefinition) { + console.error('No form definition found for criteria type', criteria.type) + return '' } - - return (filterBuilders[criterion.type]?.(criterion, deidentified) || []) - .filter((elem) => elem) - .reduce(filterReducer, '') + return constructFhirFilterForType(criteria, deidentified, formDefinition) } const mapCriteriaToResource = (criteriaType: CriteriaType): ResourceType => { @@ -773,12 +215,57 @@ const mapCriteriaToResource = (criteriaType: CriteriaType): ResourceType => { throw new Error('Unknown criteria type') } +const findQuestionnaireName = (filters: string[]) => { + for (const item of filters) { + const match = item.match(/questionnaire.name=(.*)/) + if (match?.[1]) { + return match[1] + } + } +} + +export const unbuildCriteriaData = async ( + element: RequeteurCriteriaType, + critieriaDefinitions: CriteriaItemType[] +): Promise => { + const criteriaDefinition = critieriaDefinitions.filter((item) => + isString(item.formDefinition?.buildInfo?.resourceType) + ? item.formDefinition?.buildInfo?.resourceType === element.resourceType + : item.formDefinition?.buildInfo?.resourceType.includes(element.resourceType) + ) + + if (criteriaDefinition.length === 0) { + throw new Error('Criteria definition not found') + } + + if (criteriaDefinition.length === 1 && criteriaDefinition[0].formDefinition) { + return unbuildCriteriaDataFromDefinition(element, criteriaDefinition[0].id, criteriaDefinition[0].formDefinition) + } + + const splittedFilters = element.filterFhir.split('&') + const subType = findQuestionnaireName(splittedFilters) + if (subType) { + const questionnaireDefinition = criteriaDefinition.find( + (item) => item.formDefinition?.buildInfo?.subType === subType + ) + if (questionnaireDefinition?.formDefinition) { + return unbuildCriteriaDataFromDefinition( + element, + questionnaireDefinition.id, + questionnaireDefinition.formDefinition + ) + } + } + throw new Error('Criteria subtype definition not found') +} + export function buildRequest( selectedPopulation: (Hierarchy | undefined)[] | null, selectedCriteria: SelectedCriteriaType[], criteriaGroup: CriteriaGroup[], temporalConstraints: TemporalConstraintsType[] ): string { + const criteriaDefinitions = getAllCriteriaItems(criteriaList()) if (!selectedPopulation) return '' selectedPopulation = selectedPopulation.filter((elem) => elem !== undefined) const deidentified: boolean = @@ -796,19 +283,22 @@ export function buildRequest( const isGroup = itemId < 0 if (!isGroup) { // return RequeteurCriteriaType - const item: SelectedCriteriaType = selectedCriteria.find(({ id }) => id === itemId) ?? DEFAULT_CRITERIA_ERROR - + const item: SelectedCriteriaType | undefined = selectedCriteria.find(({ id }) => id === itemId) + if (!item) { + console.error('Unknown criteria id', itemId) + continue + } child = { _type: 'basicResource', _id: item.id ?? 0, name: item.title, isInclusive: item.isInclusive ?? true, resourceType: mapCriteriaToResource(item.type), - filterFhir: constructFilterFhir(item, deidentified), + filterFhir: constructFhirFilter(item, deidentified, criteriaDefinitions), occurrence: !(item.type === CriteriaType.PATIENT || item.type === CriteriaType.IPP_LIST) ? { - n: item.occurrence, - operator: item?.occurrenceComparator ?? undefined + n: item.occurrence.value, + operator: item.occurrence.comparator } : undefined } @@ -837,7 +327,9 @@ export function buildRequest( } } } - children = [...children, child] + if (child) { + children = [...children, child] + } } return children } @@ -869,1027 +361,8 @@ export function buildRequest( return JSON.stringify(json) } -const unbuildCommonCriteria = (element: RequeteurCriteriaType): Omit => { - return { - id: element._id, - isInclusive: element.isInclusive, - title: '' - } -} - -const unbuildCriteria = async ( - element: RequeteurCriteriaType, - emptyCriterion: T, - filterUnbuilders: Partial<{ [key: string]: (c: T, v: string | null) => Promise | void }> -): Promise => { - if (emptyCriterion.type !== CriteriaType.PATIENT && emptyCriterion.type !== CriteriaType.IPP_LIST) - unbuildOccurrence(element, emptyCriterion as SelectedCriteriaTypesWithOccurrences) - - if (element.filterFhir) { - const filters = element.filterFhir.split('&').map((elem) => elem.split('=')) - if (emptyCriterion.type === CriteriaType.OBSERVATION) - unbuildObservationValueFilter(filters, emptyCriterion as ObservationDataType) - - for (const filter of filters) { - const key = filter[0] ?? null - const value = filter[1] ?? null - if (key !== null) await filterUnbuilders[key]?.(emptyCriterion, value) - else emptyCriterion.error = true - } - } - return emptyCriterion -} - -const unbuildPatientCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: DemographicDataType = { - ...unbuildCommonCriteria(element), - type: CriteriaType.PATIENT, - title: element.name ?? 'Critère démographique', - genders: [], - vitalStatus: [], - age: [null, null], - birthdates: [null, null], - deathDates: [null, null] - } - - return await unbuildCriteria(element, currentCriterion, { - [PatientsParamsKeys.DATE_IDENTIFIED]: (c, v) => { - if (v?.includes('ge')) { - c.age[0] = unbuildDurationFilter(v, false) - } else if (v?.includes('le')) { - c.age[1] = unbuildDurationFilter(v, false) - } - }, - [PatientsParamsKeys.DATE_DEIDENTIFIED]: (c, v) => { - if (v?.includes('ge')) { - c.age[0] = unbuildDurationFilter(v, true) - } else if (v?.includes('le')) { - c.age[1] = unbuildDurationFilter(v, true) - } - }, - [PatientsParamsKeys.BIRTHDATE]: (c, v) => { - if (v?.includes('ge')) { - c.birthdates[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.birthdates[1] = unbuildDateFilter(v) - } - }, - [PatientsParamsKeys.DEATHDATE]: (c, v) => { - if (v?.includes('ge')) { - c.deathDates[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.deathDates[1] = unbuildDateFilter(v) - } - }, - [PatientsParamsKeys.GENDERS]: (c, v) => { - unbuildLabelObjectFilter(c, 'genders', v) - }, - [PatientsParamsKeys.VITAL_STATUS]: (c, v) => { - unbuildLabelObjectFilter(c, 'vitalStatus', v) - } - }) -} - -const unbuildEncounterCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: EncounterDataType = { - ...unbuildCommonCriteria(element), - type: CriteriaType.ENCOUNTER, - title: element.name ?? 'Critère de prise en charge', - duration: [null, null], - age: [null, null], - admissionMode: [], - entryMode: [], - exitMode: [], - priseEnChargeType: [], - typeDeSejour: [], - reason: [], - destination: [], - provenance: [], - admission: [], - encounterService: [], - occurrence: null, - startOccurrence: [null, null], - encounterStartDate: [null, null], - encounterEndDate: [null, null], - encounterStatus: [] - } - - return await unbuildCriteria(element, currentCriterion, { - [EncounterParamsKeys.DURATION]: (c, v) => { - if (v?.includes('ge')) { - c.duration[0] = unbuildDurationFilter(v) - } else if (v?.includes('le')) { - c.duration[1] = unbuildDurationFilter(v) - } - }, - [EncounterParamsKeys.MIN_BIRTHDATE_DAY]: (c, v) => { - if (v?.includes('ge')) { - c.age[0] = unbuildDurationFilter(v) - } else if (v?.includes('le')) { - c.age[1] = unbuildDurationFilter(v) - } - }, - [EncounterParamsKeys.MIN_BIRTHDATE_MONTH]: (c, v) => { - if (v?.includes('ge')) { - c.age[0] = unbuildDurationFilter(v, true) - } else if (v?.includes('le')) { - c.age[1] = unbuildDurationFilter(v, true) - } - }, - [EncounterParamsKeys.ENTRYMODE]: (c, v) => { - unbuildLabelObjectFilter(c, 'entryMode', v) - }, - [EncounterParamsKeys.EXITMODE]: (c, v) => { - unbuildLabelObjectFilter(c, 'exitMode', v) - }, - [EncounterParamsKeys.PRISENCHARGETYPE]: (c, v) => { - unbuildLabelObjectFilter(c, 'priseEnChargeType', v) - }, - [EncounterParamsKeys.TYPEDESEJOUR]: (c, v) => { - unbuildLabelObjectFilter(c, 'typeDeSejour', v) - }, - [EncounterParamsKeys.REASON]: (c, v) => { - unbuildLabelObjectFilter(c, 'reason', v) - }, - [EncounterParamsKeys.ADMISSIONMODE]: (c, v) => { - unbuildLabelObjectFilter(c, 'admissionMode', v) - }, - [EncounterParamsKeys.DESTINATION]: (c, v) => { - unbuildLabelObjectFilter(c, 'destination', v) - }, - [EncounterParamsKeys.PROVENANCE]: (c, v) => { - unbuildLabelObjectFilter(c, 'provenance', v) - }, - [EncounterParamsKeys.ADMISSION]: (c, v) => { - unbuildLabelObjectFilter(c, 'admission', v) - }, - [EncounterParamsKeys.SERVICE_PROVIDER]: async (c, v) => { - await unbuildEncounterServiceCriterias(c, 'encounterService', v) - }, - [EncounterParamsKeys.STATUS]: (c, v) => { - unbuildLabelObjectFilter(c, 'encounterStatus', v) - }, - [EncounterParamsKeys.START_DATE]: (c, v) => { - if (v?.includes('ge')) { - c.encounterStartDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterStartDate[1] = unbuildDateFilter(v) - } - }, - [EncounterParamsKeys.END_DATE]: (c, v) => { - if (v?.includes('ge')) { - c.encounterEndDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterEndDate[1] = unbuildDateFilter(v) - } - }, - ['_filter']: (c, v) => { - unbuildEncounterDatesFilters(c, v) - } - }) -} - -const unbuildDocumentReferenceCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: DocumentDataType = { - ...unbuildCommonCriteria(element), - type: CriteriaType.DOCUMENTS, - title: element.name ?? 'Critère de document', - search: '', - searchBy: SearchByTypes.TEXT, - docType: [], - docStatuses: [], - occurrence: null, - occurrenceComparator: null, - startOccurrence: [null, null], - encounterService: [], - encounterEndDate: [null, null], - encounterStartDate: [null, null], - encounterStatus: [] - } - return await unbuildCriteria(element, currentCriterion, { - [SearchByTypes.DESCRIPTION]: (c, v) => { - c.search = unbuildSearchFilter(v) - c.searchBy = SearchByTypes.DESCRIPTION - }, - [SearchByTypes.TEXT]: (c, v) => { - c.search = unbuildSearchFilter(v) - c.searchBy = SearchByTypes.TEXT - }, - [DocumentsParamsKeys.DOC_TYPES]: (c, v) => { - unbuildDocTypesFilter(c, 'docType', v) - }, - [DocumentsParamsKeys.EXECUTIVE_UNITS]: async (c, v) => { - await unbuildEncounterServiceCriterias(c, 'encounterService', v) - }, - [DocumentsParamsKeys.DOC_STATUSES]: (c, v) => { - unbuildDocStatusesFilter(c, 'docStatuses', v) - }, - [DocumentsParamsKeys.ENCOUNTER_STATUS]: (c, v) => { - unbuildLabelObjectFilter(c, 'encounterStatus', v) - }, - [DocumentsParamsKeys.DATE]: (c, v) => { - if (v?.includes('ge')) { - c.startOccurrence[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.startOccurrence[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.DOCUMENTS)}.${EncounterParamsKeys.START_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterStartDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterStartDate[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.DOCUMENTS)}.${EncounterParamsKeys.END_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterEndDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterEndDate[1] = unbuildDateFilter(v) - } - }, - ['_filter']: (c, v) => { - unbuildEncounterDatesFilters(c, v) - } - }) -} - -const unbuildConditionCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: Cim10DataType = { - ...unbuildCommonCriteria(element), - type: CriteriaType.CONDITION, - title: element.name ?? 'Critère de diagnostic', - code: [], - source: null, - diagnosticType: [], - occurrence: null, - startOccurrence: [null, null], - encounterService: [], - encounterEndDate: [null, null], - encounterStartDate: [null, null], - occurrenceComparator: null, - label: undefined, - encounterStatus: [] - } - - return await unbuildCriteria(element, currentCriterion, { - [ConditionParamsKeys.CODE]: (c, v) => unbuildLabelObjectFilter(c, 'code', v), - [ConditionParamsKeys.SOURCE]: (c, v) => (c.source = v), - [ConditionParamsKeys.DIAGNOSTIC_TYPES]: (c, v) => unbuildLabelObjectFilter(c, 'diagnosticType', v), - [ConditionParamsKeys.EXECUTIVE_UNITS]: async (c, v) => - await unbuildEncounterServiceCriterias(c, 'encounterService', v), - [ConditionParamsKeys.ENCOUNTER_STATUS]: (c, v) => unbuildLabelObjectFilter(c, 'encounterStatus', v), - [ConditionParamsKeys.DATE]: (c, v) => { - if (v?.includes('ge')) { - c.startOccurrence[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.startOccurrence[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.CONDITION)}.${EncounterParamsKeys.START_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterStartDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterStartDate[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.CONDITION)}.${EncounterParamsKeys.END_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterEndDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterEndDate[1] = unbuildDateFilter(v) - } - }, - ['_filter']: (c, v) => unbuildEncounterDatesFilters(c, v) - }) -} - -const unbuildProcedureCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: CcamDataType = { - ...unbuildCommonCriteria(element), - type: CriteriaType.PROCEDURE, - title: element.name ?? "Critères d'actes CCAM", - code: [], - occurrence: null, - startOccurrence: [null, null], - source: null, - label: undefined, - hierarchy: undefined, - encounterService: [], - occurrenceComparator: null, - encounterStatus: [], - encounterStartDate: [null, null], - encounterEndDate: [null, null] - } - - return await unbuildCriteria(element, currentCriterion, { - [ProcedureParamsKeys.CODE]: (c, v) => unbuildLabelObjectFilter(c, 'code', v), - [ProcedureParamsKeys.EXECUTIVE_UNITS]: async (c, v) => - await unbuildEncounterServiceCriterias(c, 'encounterService', v), - [ProcedureParamsKeys.SOURCE]: (c, v) => (c.source = v), - [ProcedureParamsKeys.ENCOUNTER_STATUS]: (c, v) => unbuildLabelObjectFilter(c, 'encounterStatus', v), - [ProcedureParamsKeys.DATE]: (c, v) => { - if (v?.includes('ge')) { - c.startOccurrence[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.startOccurrence[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.PROCEDURE)}.${EncounterParamsKeys.START_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterStartDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterStartDate[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.PROCEDURE)}.${EncounterParamsKeys.END_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterEndDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterEndDate[1] = unbuildDateFilter(v) - } - }, - ['_filter']: (c, v) => unbuildEncounterDatesFilters(c, v) - }) -} - -const unbuildClaimCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: GhmDataType = { - ...unbuildCommonCriteria(element), - type: CriteriaType.CLAIM, - title: element.name ?? 'Critère de GHM', - code: [], - occurrence: null, - startOccurrence: [null, null], - encounterService: [], - label: undefined, - encounterStatus: [], - encounterStartDate: [null, null], - encounterEndDate: [null, null] - } - - return await unbuildCriteria(element, currentCriterion, { - [ClaimParamsKeys.CODE]: (c, v) => unbuildLabelObjectFilter(c, 'code', v), - [ClaimParamsKeys.EXECUTIVE_UNITS]: async (c, v) => await unbuildEncounterServiceCriterias(c, 'encounterService', v), - [ClaimParamsKeys.ENCOUNTER_STATUS]: (c, v) => unbuildLabelObjectFilter(c, 'encounterStatus', v), - [ClaimParamsKeys.DATE]: (c, v) => { - if (v?.includes('ge')) { - c.startOccurrence[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.startOccurrence[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.CLAIM)}.${EncounterParamsKeys.START_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterStartDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterStartDate[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.CLAIM)}.${EncounterParamsKeys.END_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterEndDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterEndDate[1] = unbuildDateFilter(v) - } - }, - ['_filter']: (c, v) => unbuildEncounterDatesFilters(c, v) - }) -} -const unbuildMedicationCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: MedicationDataType = { - ...unbuildCommonCriteria(element), - title: element.name ?? 'Critère de médicament', - type: - element.resourceType === ResourceType.MEDICATION_REQUEST - ? CriteriaType.MEDICATION_REQUEST - : CriteriaType.MEDICATION_ADMINISTRATION, - code: [], - prescriptionType: [], - administration: [], - occurrence: null, - startOccurrence: [null, null], - endOccurrence: [null, null], - encounterService: [], - encounterStatus: [], - encounterStartDate: [null, null], - encounterEndDate: [null, null] - } - - return await unbuildCriteria(element, currentCriterion, { - [PrescriptionParamsKeys.CODE]: (c, v) => { - const codeIds = v?.replace(/https:\/\/.*?\|/g, '') - unbuildLabelObjectFilter(c, 'code', codeIds) - }, - [PrescriptionParamsKeys.PRESCRIPTION_TYPES]: (c, v) => unbuildLabelObjectFilter(c, 'prescriptionType', v), - [PrescriptionParamsKeys.PRESCRIPTION_ROUTES || AdministrationParamsKeys.ADMINISTRATION_ROUTES]: (c, v) => - unbuildLabelObjectFilter(c, 'administration', v), - [PrescriptionParamsKeys.EXECUTIVE_UNITS || AdministrationParamsKeys.EXECUTIVE_UNITS]: async (c, v) => - await unbuildEncounterServiceCriterias(c, 'encounterService', v), - [PrescriptionParamsKeys.ENCOUNTER_STATUS || AdministrationParamsKeys.ENCOUNTER_STATUS]: (c, v) => - unbuildLabelObjectFilter(c, 'encounterStatus', v), - [PrescriptionParamsKeys.DATE || AdministrationParamsKeys.DATE]: (c, v) => { - if (v?.includes('ge')) { - c.startOccurrence[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.startOccurrence[1] = unbuildDateFilter(v) - } - }, - [PrescriptionParamsKeys.END_DATE]: (c, v) => { - if (v?.includes('ge')) { - c.endOccurrence = [unbuildDateFilter(v), c.endOccurrence?.[1] ?? null] - } else if (v?.includes('le')) { - c.endOccurrence = [c.endOccurrence?.[0] ?? null, unbuildDateFilter(v)] - } - }, - [`${getCriterionDateFilterName(CriteriaType.MEDICATION_REQUEST || CriteriaType.MEDICATION_ADMINISTRATION)}.${ - EncounterParamsKeys.START_DATE - }`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterStartDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterStartDate[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.MEDICATION_REQUEST || CriteriaType.MEDICATION_ADMINISTRATION)}.${ - EncounterParamsKeys.END_DATE - }`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterEndDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterEndDate[1] = unbuildDateFilter(v) - } - }, - ['_filter']: (c, v) => unbuildEncounterDatesFilters(c, v) - }) -} -const unbuildObservationCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: ObservationDataType = { - ...unbuildCommonCriteria(element), - type: CriteriaType.OBSERVATION, - title: element.name ?? 'Critère de biologie', - code: [], - isLeaf: false, - occurrence: null, - startOccurrence: [null, null], - encounterService: [], - searchByValue: [null, null], - valueComparator: Comparators.GREATER_OR_EQUAL, - encounterStatus: [], - encounterStartDate: [null, null], - encounterEndDate: [null, null] - } - - return await unbuildCriteria(element, currentCriterion, { - [ObservationParamsKeys.ANABIO_LOINC]: async (c, v) => { - unbuildLabelObjectFilter(c, 'code', v) - - // TODO: pas propre vvvv - if (currentCriterion.code && currentCriterion.code.length === 1) { - try { - const checkChildrenResp = await services.cohortCreation.fetchBiologyHierarchy(currentCriterion.code?.[0].id) - - if (checkChildrenResp.length === 0) { - currentCriterion.isLeaf = true - } - } catch (error) { - console.error('Erreur lors du check des enfants du code de biologie sélectionné', error) - } - } - }, - [ObservationParamsKeys.EXECUTIVE_UNITS]: async (c, v) => - await unbuildEncounterServiceCriterias(c, 'encounterService', v), - [ObservationParamsKeys.ENCOUNTER_STATUS]: (c, v) => unbuildLabelObjectFilter(c, 'encounterStatus', v), - [ObservationParamsKeys.DATE]: (c, v) => { - if (v?.includes('ge')) { - c.startOccurrence[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.startOccurrence[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.OBSERVATION)}.${EncounterParamsKeys.START_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterStartDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterStartDate[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.OBSERVATION)}.${EncounterParamsKeys.END_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterEndDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterEndDate[1] = unbuildDateFilter(v) - } - }, - ['_filter']: (c, v) => unbuildEncounterDatesFilters(c, v) - }) -} -const unbuildIPPListCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: IPPListDataType = { - ...unbuildCommonCriteria(element), - type: CriteriaType.IPP_LIST, - title: 'Critère de liste IPP', - search: '' - } - - return await unbuildCriteria(element, currentCriterion, { - [IppParamsKeys.IPP_LIST_FHIR]: (c, v) => { - c.search = v ?? '' - } - }) -} - -const unbuildImagingCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: ImagingDataType = { - ...unbuildCommonCriteria(element), - type: CriteriaType.IMAGING, - title: element.name ?? "Critère d'Imagerie", - studyStartDate: null, - studyEndDate: null, - studyModalities: [], - studyDescription: '', - studyProcedure: '', - numberOfSeries: 1, - seriesComparator: Comparators.GREATER_OR_EQUAL, - numberOfIns: 1, - instancesComparator: Comparators.GREATER_OR_EQUAL, - withDocument: DocumentAttachmentMethod.NONE, - daysOfDelay: null, - studyUid: '', - seriesStartDate: null, - seriesEndDate: null, - seriesDescription: '', - seriesProtocol: '', - seriesModalities: [], - seriesUid: '', - occurrence: null, - startOccurrence: [null, null], - encounterStartDate: [null, null], - encounterEndDate: [null, null], - encounterService: [], - encounterStatus: [] - } - - return await unbuildCriteria(element, currentCriterion, { - [ImagingParamsKeys.DATE]: (c, v) => { - if (v?.includes('ge')) { - c.studyStartDate = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.studyEndDate = unbuildDateFilter(v) - } - }, - [ImagingParamsKeys.MODALITY]: (c, v) => { - const modalitiesValues = v?.replace(/^[*|]+/, '') - unbuildLabelObjectFilter(c, 'studyModalities', modalitiesValues) - }, - [ImagingParamsKeys.STUDY_DESCRIPTION]: (c, v) => { - c.studyDescription = unbuildSearchFilter(v) - }, - [ImagingParamsKeys.STUDY_PROCEDURE]: (c, v) => { - c.studyProcedure = unbuildSearchFilter(v) - }, - [ImagingParamsKeys.NB_OF_SERIES]: (c, v) => { - const parsedOccurence = v ? parseOccurence(v) : null - c.numberOfSeries = parsedOccurence !== null ? parsedOccurence.value : 1 - c.seriesComparator = parsedOccurence !== null ? parsedOccurence.comparator : Comparators.GREATER_OR_EQUAL - }, - [ImagingParamsKeys.NB_OF_INS]: (c, v) => { - const parsedOccurence = v ? parseOccurence(v) : null - c.numberOfIns = parsedOccurence !== null ? parsedOccurence.value : 1 - c.instancesComparator = parsedOccurence !== null ? parsedOccurence.comparator : Comparators.GREATER_OR_EQUAL - }, - [ImagingParamsKeys.WITH_DOCUMENT]: (c, v) => { - const parsedDocumentAttachment = parseDocumentAttachment(v as DocumentAttachmentMethod) - c.withDocument = parsedDocumentAttachment.documentAttachmentMethod - c.daysOfDelay = parsedDocumentAttachment.daysOfDelay - }, - [ImagingParamsKeys.STUDY_UID]: (c, v) => { - c.studyUid = v?.replace(`${getConfig().features.imaging.extensions.imagingStudyUidUrl}|`, '') ?? '' - }, - [ImagingParamsKeys.SERIES_DATE]: (c, v) => { - if (v?.includes('ge')) { - c.seriesStartDate = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.seriesEndDate = unbuildDateFilter(v) - } - }, - [ImagingParamsKeys.SERIES_DESCRIPTION]: (c, v) => { - c.seriesDescription = unbuildSearchFilter(v) - }, - [ImagingParamsKeys.SERIES_PROTOCOL]: (c, v) => { - c.seriesProtocol = unbuildSearchFilter(v) - }, - [ImagingParamsKeys.SERIES_MODALITIES]: (c, v) => { - const modalitiesValues = v?.replace(/^[*|]+/, '') - unbuildLabelObjectFilter(c, 'seriesModalities', modalitiesValues) - }, - [ImagingParamsKeys.SERIES_UID]: (c, v) => { - c.seriesUid = v ?? '' - }, - [ImagingParamsKeys.EXECUTIVE_UNITS]: async (c, v) => { - await unbuildEncounterServiceCriterias(c, 'encounterService', v) - }, - [ImagingParamsKeys.ENCOUNTER_STATUS]: (c, v) => { - unbuildLabelObjectFilter(c, 'encounterStatus', v) - }, - [`${getCriterionDateFilterName(CriteriaType.IMAGING)}.${EncounterParamsKeys.START_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterStartDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterStartDate[1] = unbuildDateFilter(v) - } - }, - [`${getCriterionDateFilterName(CriteriaType.IMAGING)}.${EncounterParamsKeys.END_DATE}`]: (c, v) => { - if (v?.includes('ge')) { - c.encounterEndDate[0] = unbuildDateFilter(v) - } else if (v?.includes('le')) { - c.encounterEndDate[1] = unbuildDateFilter(v) - } - }, - ['_filter']: (c, v) => { - unbuildEncounterDatesFilters(c, v) - } - }) -} - -const unbuildPregnancyQuestionnaireResponseCriteria = async ( - element: RequeteurCriteriaType -): Promise => { - const currentCriterion: PregnancyDataType = { - ...unbuildCommonCriteria(element), - type: CriteriaType.PREGNANCY, - title: element.name ?? 'Critère de Fiche de grossesse', - pregnancyStartDate: null, - pregnancyEndDate: null, - pregnancyMode: [], - foetus: 0, - foetusComparator: Comparators.GREATER_OR_EQUAL, - parity: 0, - parityComparator: Comparators.GREATER_OR_EQUAL, - maternalRisks: [], - maternalRisksPrecision: '', - risksRelatedToObstetricHistory: [], - risksRelatedToObstetricHistoryPrecision: '', - risksOrComplicationsOfPregnancy: [], - risksOrComplicationsOfPregnancyPrecision: '', - corticotherapie: [], - prenatalDiagnosis: [], - ultrasoundMonitoring: [], - occurrence: null, - encounterService: [], - startOccurrence: [null, null], - encounterStatus: [] - } - - unbuildOccurrence(element, currentCriterion) - - if (element.filterFhir) { - const splittedFilters = element.filterFhir.split('&') - const cleanedFilters = unbuildQuestionnaireFilters(splittedFilters) - - for (const { key, values } of cleanedFilters) { - // this is bad design, we should properly handle multiple values and operators - const { value: singleValue, operator } = values.length > 0 ? values[0] : { value: '', operator: 'eq' } - const joinedValues = values.map((val) => val.value).join(',') - - switch (key) { - case pregnancyForm.pregnancyStartDate.id: - if (operator?.includes('ge')) { - currentCriterion.pregnancyStartDate = unbuildDateFilter(singleValue) - } else if (operator === 'le') { - currentCriterion.pregnancyEndDate = unbuildDateFilter(singleValue) - } - break - case pregnancyForm.pregnancyMode.id: - unbuildLabelObjectFilter(currentCriterion, 'pregnancyMode', joinedValues) - break - case pregnancyForm.foetus.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.foetus = parsedOccurence.value - currentCriterion.foetusComparator = parsedOccurence.comparator - break - } - case pregnancyForm.parity.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.parity = parsedOccurence.value - currentCriterion.parityComparator = parsedOccurence.comparator - break - } - case pregnancyForm.maternalRisks.id: - unbuildLabelObjectFilter(currentCriterion, 'maternalRisks', joinedValues) - break - case pregnancyForm.maternalRisksPrecision.id: - currentCriterion.maternalRisksPrecision = unbuildSearchFilter(singleValue) - break - case pregnancyForm.risksRelatedToObstetricHistory.id: - unbuildLabelObjectFilter(currentCriterion, 'risksRelatedToObstetricHistory', joinedValues) - break - case pregnancyForm.risksRelatedToObstetricHistoryPrecision.id: - currentCriterion.risksRelatedToObstetricHistoryPrecision = unbuildSearchFilter(singleValue) - break - case pregnancyForm.risksOrComplicationsOfPregnancy.id: - unbuildLabelObjectFilter(currentCriterion, 'risksOrComplicationsOfPregnancy', joinedValues) - break - case pregnancyForm.risksOrComplicationsOfPregnancyPrecision.id: - currentCriterion.risksOrComplicationsOfPregnancyPrecision = unbuildSearchFilter(singleValue) - break - case pregnancyForm.corticotherapie.id: - unbuildLabelObjectFilter(currentCriterion, 'corticotherapie', joinedValues) - break - case pregnancyForm.prenatalDiagnosis.id: - unbuildLabelObjectFilter(currentCriterion, 'prenatalDiagnosis', joinedValues) - break - case pregnancyForm.ultrasoundMonitoring.id: - unbuildLabelObjectFilter(currentCriterion, 'ultrasoundMonitoring', joinedValues) - break - case QuestionnaireResponseParamsKeys.EXECUTIVE_UNITS: - await unbuildEncounterServiceCriterias(currentCriterion, 'encounterService', joinedValues) - break - case QuestionnaireResponseParamsKeys.ENCOUNTER_STATUS: { - unbuildLabelObjectFilter(currentCriterion, 'encounterStatus', joinedValues) - break - } - } - } - } - return currentCriterion -} - -const unbuildHospitQuestionnaireResponseCriteria = async (element: RequeteurCriteriaType): Promise => { - const currentCriterion: HospitDataType = { - ...unbuildCommonCriteria(element), - title: "Critère de Fiche d'hospitalisation", - type: CriteriaType.HOSPIT, - hospitReason: '', - inUteroTransfer: [], - pregnancyMonitoring: [], - vme: [], - maturationCorticotherapie: [], - chirurgicalGesture: [], - childbirth: [], - hospitalChildBirthPlace: [], - otherHospitalChildBirthPlace: [], - homeChildBirthPlace: [], - childbirthMode: [], - maturationReason: [], - maturationModality: [], - imgIndication: [], - laborOrCesareanEntry: [], - pathologyDuringLabor: [], - obstetricalGestureDuringLabor: [], - analgesieType: [], - birthDeliveryStartDate: '', // TODO : check type - birthDeliveryEndDate: '', // TODO : check type - birthDeliveryWeeks: 0, - birthDeliveryWeeksComparator: Comparators.GREATER_OR_EQUAL, - birthDeliveryDays: 0, - birthDeliveryDaysComparator: Comparators.GREATER_OR_EQUAL, - birthDeliveryWay: [], - instrumentType: [], - cSectionModality: [], - presentationAtDelivery: [], - birthMensurationsGrams: 0, - birthMensurationsGramsComparator: Comparators.GREATER_OR_EQUAL, - birthMensurationsPercentil: 0, - birthMensurationsPercentilComparator: Comparators.GREATER_OR_EQUAL, - apgar1: 0, - apgar1Comparator: Comparators.GREATER_OR_EQUAL, - apgar3: 0, - apgar3Comparator: Comparators.GREATER_OR_EQUAL, - apgar5: 0, - apgar5Comparator: Comparators.GREATER_OR_EQUAL, - apgar10: 0, - apgar10Comparator: Comparators.GREATER_OR_EQUAL, - arterialPhCord: 0, - arterialPhCordComparator: Comparators.GREATER_OR_EQUAL, - arterialCordLactates: 0, - arterialCordLactatesComparator: Comparators.GREATER_OR_EQUAL, - birthStatus: [], - postpartumHemorrhage: [], - conditionPerineum: [], - exitPlaceType: [], - feedingType: [], - complication: [], - exitFeedingMode: [], - exitDiagnostic: [], - occurrence: null, - startOccurrence: [null, null], - encounterService: [], - encounterStatus: [] - } - - if (element.filterFhir) { - const splittedFilters = element.filterFhir.split('&') - const cleanedFilters = unbuildQuestionnaireFilters(splittedFilters) - - unbuildOccurrence(element, currentCriterion) - - for (const { key, values } of cleanedFilters) { - // this is bad design, we should properly handle multiple values and operators - const { value: singleValue, operator } = values.length > 0 ? values[0] : { value: '', operator: 'eq' } - const joinedValues = values.map((val) => val.value).join(',') - - switch (key) { - case hospitForm.hospitReason.id: - currentCriterion.hospitReason = unbuildSearchFilter(singleValue) - break - case hospitForm.inUteroTransfer.id: - unbuildLabelObjectFilter(currentCriterion, 'inUteroTransfer', joinedValues) - break - case hospitForm.pregnancyMonitoring.id: - unbuildLabelObjectFilter(currentCriterion, 'pregnancyMonitoring', joinedValues) - break - case hospitForm.vme.id: - unbuildLabelObjectFilter(currentCriterion, 'vme', joinedValues) - break - case hospitForm.maturationCorticotherapie.id: - unbuildLabelObjectFilter(currentCriterion, 'maturationCorticotherapie', joinedValues) - break - case hospitForm.chirurgicalGesture.id: - unbuildLabelObjectFilter(currentCriterion, 'chirurgicalGesture', joinedValues) - break - case hospitForm.childbirth.id: - unbuildLabelObjectFilter(currentCriterion, 'childbirth', joinedValues) - break - case hospitForm.hospitalChildBirthPlace.id: - unbuildLabelObjectFilter(currentCriterion, 'hospitalChildBirthPlace', joinedValues) - break - case hospitForm.otherHospitalChildBirthPlace.id: - unbuildLabelObjectFilter(currentCriterion, 'otherHospitalChildBirthPlace', joinedValues) - break - case hospitForm.homeChildBirthPlace.id: - unbuildLabelObjectFilter(currentCriterion, 'homeChildBirthPlace', joinedValues) - break - case hospitForm.childbirthMode.id: - unbuildLabelObjectFilter(currentCriterion, 'childbirthMode', joinedValues) - break - case hospitForm.maturationReason.id: - unbuildLabelObjectFilter(currentCriterion, 'maturationReason', joinedValues) - break - case hospitForm.maturationModality.id: - unbuildLabelObjectFilter(currentCriterion, 'maturationModality', joinedValues) - break - case hospitForm.imgIndication.id: - unbuildLabelObjectFilter(currentCriterion, 'imgIndication', joinedValues) - break - case hospitForm.laborOrCesareanEntry.id: - unbuildLabelObjectFilter(currentCriterion, 'laborOrCesareanEntry', joinedValues) - break - case hospitForm.pathologyDuringLabor.id: - unbuildLabelObjectFilter(currentCriterion, 'pathologyDuringLabor', joinedValues) - break - case hospitForm.obstetricalGestureDuringLabor.id: - unbuildLabelObjectFilter(currentCriterion, 'obstetricalGestureDuringLabor', joinedValues) - break - case hospitForm.analgesieType.id: - unbuildLabelObjectFilter(currentCriterion, 'analgesieType', joinedValues) - break - case hospitForm.birthDeliveryStartDate.id: - if (operator?.includes('ge')) { - currentCriterion.birthDeliveryStartDate = unbuildDateFilter(singleValue) - } else if (operator === 'le') { - currentCriterion.birthDeliveryEndDate = unbuildDateFilter(singleValue) - } - break - case hospitForm.birthDeliveryWeeks.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.birthDeliveryWeeks = parsedOccurence.value - currentCriterion.birthDeliveryWeeksComparator = parsedOccurence.comparator - break - } - case hospitForm.birthDeliveryDays.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.birthDeliveryDays = parsedOccurence.value - currentCriterion.birthDeliveryDaysComparator = parsedOccurence.comparator - break - } - case hospitForm.birthDeliveryWay.id: - unbuildLabelObjectFilter(currentCriterion, 'birthDeliveryWay', joinedValues) - break - case hospitForm.instrumentType.id: - unbuildLabelObjectFilter(currentCriterion, 'instrumentType', joinedValues) - break - case hospitForm.cSectionModality.id: - unbuildLabelObjectFilter(currentCriterion, 'cSectionModality', joinedValues) - break - case hospitForm.presentationAtDelivery.id: - unbuildLabelObjectFilter(currentCriterion, 'presentationAtDelivery', joinedValues) - break - case hospitForm.birthMensurationsGrams.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.birthMensurationsGrams = parsedOccurence.value - currentCriterion.birthMensurationsGramsComparator = parsedOccurence.comparator - break - } - case hospitForm.birthMensurationsPercentil.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.birthMensurationsPercentil = parsedOccurence.value - currentCriterion.birthMensurationsPercentilComparator = parsedOccurence.comparator - break - } - case hospitForm.apgar1.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.apgar1 = parsedOccurence.value - currentCriterion.apgar1Comparator = parsedOccurence.comparator - break - } - case hospitForm.apgar3.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.apgar3 = parsedOccurence.value - currentCriterion.apgar3Comparator = parsedOccurence.comparator - break - } - case hospitForm.apgar5.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.apgar5 = parsedOccurence.value - currentCriterion.apgar5Comparator = parsedOccurence.comparator - break - } - case hospitForm.apgar10.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.apgar10 = parsedOccurence.value - currentCriterion.apgar10Comparator = parsedOccurence.comparator - break - } - case hospitForm.arterialPhCord.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.arterialPhCord = parsedOccurence.value - currentCriterion.arterialPhCordComparator = parsedOccurence.comparator - break - } - case hospitForm.arterialCordLactates.id: { - const _value = `${operator}${singleValue}` - const parsedOccurence = parseOccurence(_value) - currentCriterion.arterialCordLactates = parsedOccurence.value - currentCriterion.arterialCordLactatesComparator = parsedOccurence.comparator - break - } - case hospitForm.birthStatus.id: - unbuildLabelObjectFilter(currentCriterion, 'birthStatus', joinedValues) - break - case hospitForm.postpartumHemorrhage.id: - unbuildLabelObjectFilter(currentCriterion, 'postpartumHemorrhage', joinedValues) - break - case hospitForm.conditionPerineum.id: - unbuildLabelObjectFilter(currentCriterion, 'conditionPerineum', joinedValues) - break - case hospitForm.exitPlaceType.id: - unbuildLabelObjectFilter(currentCriterion, 'exitPlaceType', joinedValues) - break - case hospitForm.feedingType.id: - unbuildLabelObjectFilter(currentCriterion, 'feedingType', joinedValues) - break - case hospitForm.complication.id: - unbuildLabelObjectFilter(currentCriterion, 'complication', joinedValues) - break - case hospitForm.exitFeedingMode.id: - unbuildLabelObjectFilter(currentCriterion, 'exitFeedingMode', joinedValues) - break - case hospitForm.exitDiagnostic.id: - unbuildLabelObjectFilter(currentCriterion, 'exitDiagnostic', joinedValues) - break - case QuestionnaireResponseParamsKeys.EXECUTIVE_UNITS: - await unbuildEncounterServiceCriterias(currentCriterion, 'encounterService', joinedValues) - break - case QuestionnaireResponseParamsKeys.ENCOUNTER_STATUS: { - unbuildLabelObjectFilter(currentCriterion, 'encounterStatus', joinedValues) - break - } - } - } - } - return currentCriterion -} - -const unbuildQuestionnaireResponseCriteria = async (element: RequeteurCriteriaType): Promise => { - if (element.filterFhir) { - const splittedFilters = element.filterFhir.split('&') - const findRessource = findQuestionnaireName(splittedFilters) - - switch (findRessource) { - case FormNames.PREGNANCY: - return unbuildPregnancyQuestionnaireResponseCriteria(element) - case FormNames.HOSPIT: - return unbuildHospitQuestionnaireResponseCriteria(element) - default: - break - } - } - throw new Error('Unknown questionnaire response type') -} - export async function unbuildRequest(_json: string): Promise { + const criteriaDefinitions = getAllCriteriaItems(criteriaList()) // TODO: handle potential errors (here or in the caller) // so if a single criteria fails, the whole request is not lost // let population: (ScopeTreeRow | undefined)[] | null = null @@ -1942,35 +415,16 @@ export async function unbuildRequest(_json: string): Promise { return { population, criteria: [], criteriaGroup: [] } } - const _retrieveInformationFromJson = async (element: RequeteurCriteriaType): Promise => { - const unbuildMapper: { [key in ResourceType]?: (el: RequeteurCriteriaType) => Promise } = { - [ResourceType.PATIENT]: (el) => Promise.resolve(unbuildPatientCriteria(el)), - [ResourceType.ENCOUNTER]: unbuildEncounterCriteria, - [ResourceType.DOCUMENTS]: unbuildDocumentReferenceCriteria, - [ResourceType.CONDITION]: unbuildConditionCriteria, - [ResourceType.PROCEDURE]: unbuildProcedureCriteria, - [ResourceType.CLAIM]: unbuildClaimCriteria, - [ResourceType.MEDICATION_REQUEST]: unbuildMedicationCriteria, - [ResourceType.MEDICATION_ADMINISTRATION]: unbuildMedicationCriteria, - [ResourceType.OBSERVATION]: unbuildObservationCriteria, - [ResourceType.IPP_LIST]: (el) => Promise.resolve(unbuildIPPListCriteria(el)), - [ResourceType.IMAGING]: unbuildImagingCriteria, - [ResourceType.QUESTIONNAIRE_RESPONSE]: unbuildQuestionnaireResponseCriteria - } - const unbuild = unbuildMapper[element.resourceType] - if (unbuild) { - return unbuild(element) - } - throw new Error('Unknown resource type') - } - const convertJsonObjectsToCriteria = async ( _criteriaItems: RequeteurCriteriaType[] ): Promise => { let newSelectedCriteriaItems: SelectedCriteriaType[] = [] for (const criteriaItem of _criteriaItems) { - newSelectedCriteriaItems = [...newSelectedCriteriaItems, await _retrieveInformationFromJson(criteriaItem)] + newSelectedCriteriaItems = [ + ...newSelectedCriteriaItems, + await unbuildCriteriaData(criteriaItem, criteriaDefinitions) + ] } return newSelectedCriteriaItems @@ -2075,84 +529,44 @@ export async function unbuildRequest(_json: string): Promise { } /** - * This function calls all functions to fetch data contained inside `src/components/CreationCohort/DataList_Criteria` list - * + * Fetches all codes for the criteria within the query + * @param criteriaList list of criteria definitions + * @param selectedCriteria list of criteria data + * @param oldCriteriaCache old cache of criteria codes + * @returns a newly updated cache of criteria codes */ -export const getDataFromFetch = async ( +export const fetchCriteriasCodes = async ( criteriaList: readonly CriteriaItemType[], selectedCriteria: SelectedCriteriaType[], - oldCriteriaCache?: CriteriaItemDataCache[] -): Promise => { - const updatedCriteriaData: CriteriaItemDataCache[] = [] - for (const _criterion of criteriaList) { - const criteriaDataCache: CriteriaItemDataCache = { - data: {}, - criteriaType: _criterion.id - } - // here we do not populate new data with old data because the store froze the data (readonly) so they can't be updated - const prevDataCache: CriteriaItemDataCache['data'] = - oldCriteriaCache?.find((oldCriterionItem) => oldCriterionItem.criteriaType === _criterion.id)?.data || {} - - if (_criterion.fetch) { - const dataKeys = Object.keys(_criterion.fetch) as CriteriaDataKey[] - - for (const dataKey of dataKeys) { - switch (dataKey) { - case CriteriaDataKey.MEDICATION_DATA: - case CriteriaDataKey.BIOLOGY_DATA: - case CriteriaDataKey.GHM_DATA: - case CriteriaDataKey.CCAM_DATA: - case CriteriaDataKey.CIM_10_DIAGNOSTIC: { - const currentSelectedCriteria = selectedCriteria.filter( - (criterion: SelectedCriteriaType) => - criterion.type === _criterion.id || - // V-- [ Link with Medication and `MedicationAdministration` or `MedicationRequest` ] - (_criterion.id === 'Medication' && - (criterion.type === CriteriaType.MEDICATION_REQUEST || - criterion.type === CriteriaType.MEDICATION_ADMINISTRATION)) - ) - - if (currentSelectedCriteria) { - for (const currentcriterion of currentSelectedCriteria) { - if ( - currentcriterion && - !( - currentcriterion.type === CriteriaType.PATIENT || - currentcriterion.type === CriteriaType.ENCOUNTER || - currentcriterion.type === CriteriaType.IPP_LIST || - currentcriterion.type === CriteriaType.DOCUMENTS || - currentcriterion.type === CriteriaType.IMAGING || - currentcriterion.type === CriteriaType.PREGNANCY || - currentcriterion.type === CriteriaType.HOSPIT - ) && - currentcriterion.code && - currentcriterion.code.length > 0 - ) { - for (const code of currentcriterion.code) { - const prevData = prevDataCache[dataKey]?.find((data: any) => data.id === code?.id) - const codeData = prevData ? [prevData] : await _criterion.fetch[dataKey]?.(code?.id, true) - const existingCodes = criteriaDataCache.data[dataKey] || [] - criteriaDataCache.data[dataKey] = [...existingCodes, ...(codeData || [])] - } - } + oldCriteriaCache?: CodeCache +): Promise => { + const updatedCriteriaData: CodeCache = { ...oldCriteriaCache } + const allCriterias = getAllCriteriaItems(criteriaList) + for (const criteria of allCriterias) { + const criteriaValues = selectedCriteria.filter((criterion) => criterion.type === criteria.id) + for (const section of criteria.formDefinition?.itemSections || []) { + for (const item of section.items || []) { + if (item.type === 'codeSearch') { + const defaultValueSet = item.valueSetIds[0] + for (const criterion of criteriaValues) { + const dataKey = item.valueKey as string + // TODO remove this type casting when using the proper entry type, also make sure that the dataKey is a valid key + const labelValues = criterion[dataKey as keyof SelectedCriteriaType] as unknown as LabelObject[] + for (const code of labelValues) { + const codeSystem = code.system || defaultValueSet + const valueSetCodeCache = updatedCriteriaData[codeSystem] ?? [] + if (!valueSetCodeCache.find((data) => data.id === code.id)) { + const fetchedCode = (await fetchValueSet(codeSystem, { + code: code.id || '' + })) as LabelObject[] + valueSetCodeCache.push(...fetchedCode) } + updatedCriteriaData[codeSystem] = valueSetCodeCache } - break } - default: - criteriaDataCache.data[dataKey] = prevDataCache[dataKey] - if (criteriaDataCache.data[dataKey] === undefined) { - criteriaDataCache.data[dataKey] = await _criterion.fetch[dataKey]?.() - } - break } } } - updatedCriteriaData.push(criteriaDataCache) - - if (_criterion.subItems && _criterion.subItems.length > 0) { - updatedCriteriaData.push(...(await getDataFromFetch(_criterion.subItems, selectedCriteria, oldCriteriaCache))) - } } return updatedCriteriaData } diff --git a/src/utils/displayValueSetSystem.ts b/src/utils/displayValueSetSystem.ts deleted file mode 100644 index fb6902686..000000000 --- a/src/utils/displayValueSetSystem.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { getConfig } from 'config' - -export const displaySystem = (system?: string) => { - switch (system) { - case getConfig().features.medication.valueSets.medicationAtc.url: - return 'ATC: ' - case getConfig().features.medication.valueSets.medicationUcd.url: - return 'UCD: ' - default: - return '' - } -} diff --git a/src/utils/mappers.ts b/src/utils/mappers.ts deleted file mode 100644 index 9dbed36be..000000000 --- a/src/utils/mappers.ts +++ /dev/null @@ -1,384 +0,0 @@ -import { ScopeElement, SimpleCodeType } from 'types' -import { - DocumentAttachmentMethod, - DurationRangeType, - LabelObject, - mapDocumentStatusesFromRequestParam -} from 'types/searchCriterias' -import { - convertDurationToString, - convertDurationToTimestamp, - convertStringToDuration, - convertTimestampToDuration -} from './age' -import docTypes from 'assets/docTypes.json' -import { RequeteurCriteriaType } from './cohortCreation' -import moment from 'moment' -import { - Comparators, - CriteriaType, - CriteriaTypesWithAdvancedInputs, - ImagingDataType, - ObservationDataType, - SelectedCriteriaTypesWithOccurrences, - SelectedCriteriaTypesWithAdvancedInputs, - EncounterDataType, - EncounterParamsKeys, - ObservationParamsKeys -} from 'types/requestCriterias' -import { comparatorToFilter, parseOccurence } from './valueComparator' -import services from 'services/aphp' -import extractFilterParams, { FhirFilterValue } from './fhirFilterParser' -import { Hierarchy } from 'types/hierarchy' - -const searchReducer = (accumulator: string, currentValue: string): string => - accumulator || !!accumulator === false ? `${accumulator},${currentValue}` : currentValue || accumulator -const comparator = /(le|ge)/gi - -const replaceTime = (date?: string) => { - return date?.replace('T00:00:00Z', '') ?? null -} - -export const buildLabelObjectFilter = ( - criterion: LabelObject[] | undefined | null, - hierarchyUrl?: string, - system?: boolean -) => { - if (criterion && criterion.length > 0) { - let filter = '' - criterion.find((code) => code.id === '*') - ? (filter = `${hierarchyUrl}|*`) - : (filter = `${criterion - .map((item) => (system && item.system ? `${item.system}|${item.id}` : item.id)) - .reduce(searchReducer)}`) - return filter - } - return '' -} - -export const unbuildLabelObjectFilter = (currentCriterion: any, filterName: string, values?: string | null) => { - const valuesIds = values?.split(',') || [] - const newArray = valuesIds?.map((value) => (value.includes('|*') ? { id: '*' } : { id: value })) - if (newArray) { - currentCriterion[filterName] = currentCriterion ? [...currentCriterion[filterName], ...newArray] : newArray - } -} - -export const buildEncounterServiceFilter = (criterion?: Hierarchy[]) => { - return criterion && criterion.length > 0 ? `${criterion.map((item) => item.id).reduce(searchReducer)}` : '' -} - -export const unbuildEncounterServiceCriterias = async ( - currentCriterion: any, - filterName: string, - values?: string | null -) => { - if (values && values !== null) { - const encounterServices: ScopeElement[] = (await services.perimeters.getPerimeters({ ids: values })).results - currentCriterion[filterName] = currentCriterion - ? [...currentCriterion[filterName], ...encounterServices] - : encounterServices - } -} - -export const buildDateFilter = ( - criterion: string | null | undefined, - comparator: 'le' | 'ge', - removeTimeZone = false, - withSpace = false -) => { - const _withSpace = withSpace ? ' ' : '' - const dateFormat = `YYYY-MM-DD[T00:00:00${removeTimeZone ? '' : 'Z'}]` - - return criterion ? `${comparator}${_withSpace}${moment(criterion).format(dateFormat)}` : '' -} - -export const unbuildDateFilter = (value: string) => { - return replaceTime(value.replace(comparator, '')) -} - -export const buildDurationFilter = ( - age: string | null | undefined, - fhirKey: string, - comparator: 'le' | 'ge', - deidentified?: boolean -) => { - const convertedRange = convertDurationToTimestamp(convertStringToDuration(age), deidentified) - if (convertedRange === null) return '' - return `${fhirKey}=${comparator}${convertedRange}` -} - -export const unbuildDurationFilter = (value: string, deidentified?: boolean) => { - const cleanValue = value?.replace(comparator, '') - return convertDurationToString(convertTimestampToDuration(+cleanValue, deidentified)) -} - -export const buildSearchFilter = (criterion: string) => { - return criterion ? `${encodeURIComponent(criterion)}` : '' -} - -export const unbuildSearchFilter = (value: string | null) => { - return value !== null ? decodeURIComponent(value) : '' -} - -export const buildObservationValueFilter = (criterion: ObservationDataType, fhirKey: string) => { - const valueComparatorFilter = comparatorToFilter(criterion.valueComparator) - if ( - criterion.isLeaf && - criterion.code && - criterion.code.length === 1 && - criterion.valueComparator && - (typeof criterion.searchByValue[0] === 'number' || typeof criterion.searchByValue[1] === 'number') - ) { - if (criterion.valueComparator === Comparators.BETWEEN && criterion.searchByValue[1]) { - return `${fhirKey}=le${criterion.searchByValue[1]}&${fhirKey}=ge${criterion.searchByValue[0]}` - } else { - return `${fhirKey}=${valueComparatorFilter}${criterion.searchByValue[0]}` - } - } - return `${fhirKey}=le0,ge0` -} - -export const unbuildObservationValueFilter = (filters: string[][], currentCriterion: ObservationDataType) => { - const valueQuantities = filters - .filter((keyValue) => keyValue[0].includes(ObservationParamsKeys.VALUE)) - ?.map((value) => value[1]) - if (valueQuantities[0] === 'le0,ge0') return null - if (valueQuantities.length === 0) { - currentCriterion['valueComparator'] = Comparators.GREATER_OR_EQUAL - } else if (valueQuantities.length === 1) { - const parsedOccurence = parseOccurence(valueQuantities[0]) - currentCriterion['valueComparator'] = parsedOccurence.comparator - currentCriterion['searchByValue'] = [parsedOccurence.value, null] - } else if (valueQuantities.length === 2) { - currentCriterion['valueComparator'] = Comparators.BETWEEN - currentCriterion['searchByValue'] = [ - parseOccurence(valueQuantities.find((value) => value.startsWith('ge')) ?? '').value, - parseOccurence(valueQuantities.find((value) => value.startsWith('le')) ?? '').value - ] - } -} - -export const buildComparatorFilter = (criterion: number, comparator: Comparators) => { - return criterion ? `${comparatorToFilter(comparator)}${criterion}` : '' -} - -export const buildWithDocumentFilter = (criterion: ImagingDataType, fhirKey: string) => { - if (criterion.withDocument !== DocumentAttachmentMethod.NONE) { - return `${fhirKey}=${ - criterion.withDocument === DocumentAttachmentMethod.ACCESS_NUMBER - ? DocumentAttachmentMethod.ACCESS_NUMBER - : `INFERENCE_TEMPOREL${ - criterion.daysOfDelay !== null && criterion.daysOfDelay !== '' ? `_${criterion.daysOfDelay}_J` : '' - }` - }` - } - return '' -} - -export const unbuildDocTypesFilter = (currentCriterion: any, filterName: string, values?: string | null) => { - const valuesIds = values?.split(',') || [] - const newArray = docTypes.docTypes.filter((docType: SimpleCodeType) => - valuesIds?.find((docTypeId) => docTypeId === docType.code) - ) - if (newArray) { - currentCriterion[filterName] = currentCriterion ? [...currentCriterion[filterName], ...newArray] : newArray - } -} - -export const unbuildDocStatusesFilter = (currentCriterion: any, filterName: string, values?: string | null) => { - const newArray = values?.split(',').map((value) => mapDocumentStatusesFromRequestParam(value.split('|')[1])) - - if (newArray) { - currentCriterion[filterName] = currentCriterion ? [...currentCriterion[filterName], ...newArray] : newArray - } -} - -export const unbuildOccurrence = ( - element: RequeteurCriteriaType, - currentCriterion: SelectedCriteriaTypesWithOccurrences -) => { - if (element.occurrence) { - currentCriterion.occurrence = element.occurrence ? element.occurrence.n : 1 - currentCriterion.occurrenceComparator = element.occurrence - ? element.occurrence.operator ?? Comparators.GREATER_OR_EQUAL - : Comparators.GREATER_OR_EQUAL - } - return currentCriterion -} - -export const buildSimpleFilter = (criterion: string, fhirKey: string, url?: string) => { - return criterion ? `${fhirKey}=${url ? `${url}|` : ''}${criterion}` : '' -} - -const LINK_ID_PARAM_NAME = 'item.linkId' -const VALUE_PARAM_NAME_PREFIX = 'item.answer.' -const FILTER_PARAM_NAME = '_filter' - -const quoteValue = (value: string, type: string) => { - return ['valueString', 'valueCoding'].includes(type) ? `"${value}"` : value -} - -export const questionnaireFiltersBuilders = (fhirKey: { id: string; type: string }, value?: string) => { - const slice = value?.slice(0, 2) - const operator = slice === 'ge' || slice === 'le' || slice === 'lt' || slice === 'gt' || slice === 'eq' ? slice : 'eq' - const _value = slice === 'ge' || slice === 'le' || slice === 'lt' || slice === 'gt' ? value?.slice(2) : value - - if (fhirKey.type === 'valueBoolean' || fhirKey.type === 'valueCoding') { - const _code = value?.split(',') - return value && _code && _code?.length > 0 - ? `${FILTER_PARAM_NAME}=${LINK_ID_PARAM_NAME} eq ${fhirKey.id} and (${_code - .map((code) => `${VALUE_PARAM_NAME_PREFIX}${fhirKey.type} eq ${quoteValue(code, fhirKey.type)}`) - .join(' or ')})` - : '' - } else { - return _value - ? `${FILTER_PARAM_NAME}=${LINK_ID_PARAM_NAME} eq ${fhirKey.id} and ${VALUE_PARAM_NAME_PREFIX}${ - fhirKey.type - } ${operator} ${quoteValue(_value, fhirKey.type)}` - : '' - } -} - -export const buildEncounterDateFilter = ( - criterionType: CriteriaTypesWithAdvancedInputs | CriteriaType.ENCOUNTER, - includeNullDates?: boolean, - encounterDate?: DurationRangeType, - startDate?: boolean -) => { - const encounterDateExists = !!encounterDate && (!!encounterDate[0] || !!encounterDate[1]) - const criteriaFilterPrefix = - criterionType === CriteriaType.ENCOUNTER ? '' : `${getCriterionDateFilterName(criterionType)}.` - const criterionDateFilterName = `${criteriaFilterPrefix}${ - startDate ? EncounterParamsKeys.START_DATE : EncounterParamsKeys.END_DATE - }` - - if (includeNullDates && encounterDateExists) { - const dateFilter = `(${getDateFilters( - encounterDate, - criterionDateFilterName - )}) or not (${criterionDateFilterName} eq "*")` - - return filtersBuilders(FILTER_PARAM_NAME, dateFilter) - } else if (encounterDateExists) { - if (encounterDate[0] && encounterDate[1]) { - const startDateFilter = filtersBuilders(criterionDateFilterName, buildDateFilter(encounterDate[0], 'ge')) - const endDateFilter = filtersBuilders(criterionDateFilterName, buildDateFilter(encounterDate[1], 'le')) - - return `${startDateFilter}&${endDateFilter}` - } else { - const dateFilter = encounterDate[0] - ? buildDateFilter(encounterDate[0], 'ge') - : buildDateFilter(encounterDate[1], 'le') - - return filtersBuilders(criterionDateFilterName, dateFilter) - } - } else return '' -} - -export const getDateFilters = (dates: DurationRangeType, criterionDateFilterName: string) => { - if (dates[0] && dates[1]) { - return `${criterionDateFilterName} ${buildDateFilter( - dates[0], - 'ge', - false, - true - )} and ${criterionDateFilterName} ${buildDateFilter(dates[1], 'le', false, true)}` - } else { - return `${criterionDateFilterName} ${ - dates[0] ? buildDateFilter(dates[0], 'ge', false, true) : buildDateFilter(dates[1], 'le', false, true) - }` - } -} - -export const getCriterionDateFilterName = (criterion: CriteriaTypesWithAdvancedInputs) => { - const mapping = { - [CriteriaType.DOCUMENTS]: 'encounter', - [CriteriaType.CONDITION]: 'encounter', - [CriteriaType.PROCEDURE]: 'encounter', - [CriteriaType.CLAIM]: 'encounter', - [CriteriaType.MEDICATION_REQUEST]: 'encounter', - [CriteriaType.MEDICATION_ADMINISTRATION]: 'context', - [CriteriaType.OBSERVATION]: 'encounter', - [CriteriaType.IMAGING]: 'encounter' - } - return mapping[criterion] -} - -export const unbuildEncounterDatesFilters = ( - criterion: SelectedCriteriaTypesWithAdvancedInputs | EncounterDataType, - value: string | null -) => { - if (value?.includes(EncounterParamsKeys.START_DATE)) { - criterion.encounterStartDate = unbuildEncounterDateFilters(value) - criterion.includeEncounterStartDateNull = true - } else if (value?.includes(EncounterParamsKeys.END_DATE)) { - criterion.encounterEndDate = unbuildEncounterDateFilters(value) - criterion.includeEncounterEndDateNull = true - } - - return criterion -} - -export const unbuildEncounterDateFilters = (filter: string) => { - const datesRegex = /(le|ge)\s(\d{4})-(\d{2})-(\d{2})/g // matches dates - const dates = filter.match(datesRegex) - let formattedDates: DurationRangeType - - if (dates && dates.length > 1) { - formattedDates = [unbuildDateFilter(dates[0].split(' ').join('')), unbuildDateFilter(dates[1].split(' ').join(''))] - } else if (dates && dates.length === 1) { - const formattedDate = unbuildDateFilter(dates[0].split(' ').join('')) - formattedDates = dates[0].includes('ge') ? [formattedDate, null] : [null, formattedDate] - } else { - formattedDates = [null, null] - } - - return formattedDates -} - -export const findQuestionnaireName = (filters: string[]) => { - for (const item of filters) { - const match = item.match(/questionnaire.name=(.*)/) - if (match?.[1]) { - return match[1] - } - } -} - -export const unbuildQuestionnaireFilters = ( - filters: string[] -): Array<{ key: string; values: Array }> => { - const specialFilters = filters - .filter((filter) => filter.startsWith(`${FILTER_PARAM_NAME}=`)) - .map((filter) => { - const filterContent = filter.split(`${FILTER_PARAM_NAME}=`)[1] - const filterElements = extractFilterParams(filterContent, { omitOperatorEq: true }) - if (filterElements) { - // should check that this param exist and does not have multiple values - // and raise an error (but errors should be properly handled in the unbuildRequest function) - const paramKey = filterElements.find((element) => element.param === LINK_ID_PARAM_NAME) - const paramValues = filterElements.filter((element) => element.param.startsWith(VALUE_PARAM_NAME_PREFIX)) - const key = paramKey?.values[0].value - return { - key: key, - values: paramValues.flatMap((element) => element.values) - } - } - }) - .filter((filter) => filter !== undefined) as Array<{ key: string; values: Array }> - const standardFilters = filters - .filter((filter) => !filter.startsWith(`${FILTER_PARAM_NAME}=`)) - .map((filter) => { - const [key, value] = filter.split('=') - return { - key: key, - values: [{ value: value, operator: 'undefined' }] - } - }) - return specialFilters.concat(standardFilters) -} - -export const filtersBuilders = (fhirKey: string, value?: string) => { - return value ? `${fhirKey}=${value}` : '' -} diff --git a/src/utils/pmsi.ts b/src/utils/pmsi.ts index 96e814a62..2c9258255 100644 --- a/src/utils/pmsi.ts +++ b/src/utils/pmsi.ts @@ -10,10 +10,11 @@ import { import { expandMedicationElement } from '../state/medication' import { expandBiologyElement } from '../state/biology' import services from 'services/aphp' -import { CommonCriteriaDataType, CriteriaType, SelectedCriteriaType } from 'types/requestCriterias' +import { CriteriaType, SelectedCriteriaType } from 'types/requestCriterias' import { Condition } from 'fhir/r4' import { Hierarchy } from 'types/hierarchy' import { LabelObject } from 'types/searchCriterias' +import { CommonCriteriaData } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types' /** * @description : get the last diagnosis labels @@ -341,17 +342,6 @@ export const initSyncHierarchyTableEffect = async ( dispatch(decrementLoadingSyncHierarchyTable()) } -export const onChangeSelectedCriteriaEffect = async ( - codesToExpand: Hierarchy[], - selectedCodes: Hierarchy[], - resourceHierarchy: Hierarchy[], - resourceType: CriteriaType, - dispatch: AppDispatch -): Promise => { - await expandHierarchyCodes(codesToExpand, selectedCodes, resourceHierarchy, resourceType, dispatch) - dispatch(pushSyncHierarchyTable({ code: selectedCodes })) -} - const isExpanded = (itemToExpand: Hierarchy | undefined): boolean => { if (itemToExpand?.subItems?.length > 0 && itemToExpand?.subItems[0].id !== 'loading') { return true @@ -521,7 +511,7 @@ const expandHierarchyCodes = async ( resourceHierarchy = newResourceHierarchy return resourceHierarchy } -export const syncOnChangeFormValue = async ( +export const syncOnChangeFormValue = async ( key: string, value: any, resourceHierarchy: Hierarchy[], diff --git a/src/utils/requestCriterias.tsx b/src/utils/requestCriterias.tsx deleted file mode 100644 index 092f600ef..000000000 --- a/src/utils/requestCriterias.tsx +++ /dev/null @@ -1,955 +0,0 @@ -import React, { ReactNode } from 'react' -import moment from 'moment' -import { - Comparators, - CriteriaDataKey, - MedicationLabel, - CriteriaType, - SelectedCriteriaType, - CriteriaTypesWithAdvancedInputs -} from 'types/requestCriterias' -import { - DocumentAttachmentMethod, - DocumentAttachmentMethodLabel, - DurationRangeType, - LabelObject, - SearchByTypes -} from 'types/searchCriterias' -import allDocTypes from 'assets/docTypes.json' -import { getDurationRangeLabel } from './age' -import { displaySystem } from './displayValueSetSystem' -import { CriteriaState } from 'state/criteria' -import { Tooltip, Typography } from '@mui/material' -import { Hierarchy } from 'types/hierarchy' -import { ScopeElement, SimpleCodeType } from 'types' - -export const getOccurenceDateLabel = ( - selectedCriteriaType: Exclude, - endOccurrence?: boolean -) => { - const mapping = { - [CriteriaType.DOCUMENTS]: 'Date de création du document', - [CriteriaType.CONDITION]: 'Date du diagnostic CIM10', - [CriteriaType.PROCEDURE]: "Date de l'acte CCAM", - [CriteriaType.CLAIM]: 'Date du classement en GHM', - [CriteriaType.MEDICATION_REQUEST]: endOccurrence ? 'Date de fin de prescription' : 'Date de début de prescription', - [CriteriaType.MEDICATION_ADMINISTRATION]: "Date de début d'administration", - [CriteriaType.OBSERVATION]: "Date de l'examen" - } - - return mapping[selectedCriteriaType] -} - -const getMedicationTypeLabel = (type: CriteriaType) => { - switch (type) { - case CriteriaType.MEDICATION_REQUEST: - return MedicationLabel.PRESCRIPTION - case CriteriaType.MEDICATION_ADMINISTRATION: - return MedicationLabel.ADMINISTRATION - } -} - -const getLabelFromCriteriaObject = ( - criteriaState: CriteriaState, - values: LabelObject[] | null, - name: CriteriaDataKey, - resourceType: CriteriaType, - label?: string -) => { - const criterionData = criteriaState.cache.find((criteriaCache) => criteriaCache.criteriaType === resourceType)?.data - if (criterionData === null || values === null) return '' - - const criterion = criterionData?.[name] || [] - if (criterion !== 'loading') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const removeDuplicates = (array: any[], key: string) => { - return array.filter((obj, index, self) => index === self.findIndex((el) => el[key] === obj[key])) - } - const labels = removeDuplicates(criterion, 'id') - .filter((obj) => values.map((value) => value.id).includes(obj.id)) - .map((obj: LabelObject) => `${displaySystem(obj.system)} ${obj.label}`) - - const tooltipTitle = labels.join(' - ') - return ( - - - {label} {tooltipTitle} - - - ) - } -} - -const getLabelFromName = (values: Hierarchy[]) => { - const labels = values.map((value) => `${value.source_value} - ${value.name}`).join(' - ') - return labels -} - -const getDatesLabel = (values: DurationRangeType, word?: string, excludeNullDates?: boolean) => { - const excludeNullDatesWording = excludeNullDates ? ', valeurs nulles incluses' : '' - let datesLabel = '' - if (values[0] && values[1]) { - datesLabel = `${word ? word + ' entre' : 'Entre'} le ${moment(values[0]).format('DD/MM/YYYY')} et le ${moment( - values[1] - ).format('DD/MM/YYYY')}${excludeNullDatesWording}` - } - if (values[0] && !values[1]) { - datesLabel = `${word ? word + ' à' : 'À'} partir du ${moment(values[0]).format( - 'DD/MM/YYYY' - )}${excludeNullDatesWording}` - } - if (!values[0] && values[1]) { - datesLabel = `${word ? word + " jusqu'au" : "jusqu'au"} ${moment(values[1]).format( - 'DD/MM/YYYY' - )}${excludeNullDatesWording}` - } - - return ( - - {datesLabel} - - ) -} - -const getSearchDocumentLabel = (value: string, searchBy: SearchByTypes) => { - const loc = searchBy === SearchByTypes.TEXT ? 'document' : 'titre du document' - return `Contient "${value}" dans le ${loc}` -} - -const getDocumentTypesLabel = (values: SimpleCodeType[]) => { - const allTypes = new Set(allDocTypes.docTypes.map((docType: SimpleCodeType) => docType.type)) - - const displayingSelectedDocTypes = values.reduce((acc, selectedDocType) => { - const numberOfElementFromGroup = allTypes.has(selectedDocType.type) ? allTypes.size : 0 - const numberOfElementSelected = values.filter((doc) => doc.type === selectedDocType.type).length - - if (numberOfElementFromGroup === numberOfElementSelected) { - return acc - } else { - return [...acc, selectedDocType] - } - }, [] as SimpleCodeType[]) - - const currentDocTypes = displayingSelectedDocTypes.map(({ label }) => label).join(' - ') - - return currentDocTypes -} - -const getNbOccurencesLabel = (value: number, comparator: string, name: string) => { - return `${name} ${comparator} ${+value}` -} -const getDocumentStatusLabel = (value: string[]) => { - return `Statut de documents : ${value.join(', ')}` -} - -const getBiologyValuesLabel = (comparator: string, values: [number | null, number | null]) => { - if (values[0] === null && values[1] === null) return null - return comparator === Comparators.BETWEEN - ? `Valeur comprise entre ${values[0]} et ${values[1] === null ? '?' : values[1]}` - : `Valeur ${comparator} ${values[0]}` -} - -const getIdsListLabels = (values: string, name: string) => { - const labels = values.split(',').join(' - ') - return `Contient les ${name} : ${labels}` -} - -export const getAttachmentMethod = (value: DocumentAttachmentMethod, daysOfDelay: string | null) => { - if (value === DocumentAttachmentMethod.INFERENCE_TEMPOREL) { - return `Rattachement aux documents par ${DocumentAttachmentMethodLabel.INFERENCE_TEMPOREL.toLocaleLowerCase()}${ - daysOfDelay !== '' && daysOfDelay !== null ? ` de ${daysOfDelay} jour(s)` : '' - }` - } else if (value === DocumentAttachmentMethod.ACCESS_NUMBER) { - return `Rattachement aux documents par ${DocumentAttachmentMethodLabel.ACCESS_NUMBER.toLocaleLowerCase()}` - } else { - return '' - } -} - -export const criteriasAsArray = (selectedCriteria: SelectedCriteriaType, criteriaState: CriteriaState): ReactNode[] => { - const type = selectedCriteria.type - const labels: ReactNode[] = [] - switch (selectedCriteria.type) { - case CriteriaType.IPP_LIST: - labels.push(getIdsListLabels(selectedCriteria.search, 'patients')) - break - - case CriteriaType.PATIENT: - if (selectedCriteria.genders && selectedCriteria.genders.length > 0) { - labels.push(getLabelFromCriteriaObject(criteriaState, selectedCriteria.genders, CriteriaDataKey.GENDER, type)) - } - - if (selectedCriteria.vitalStatus && selectedCriteria.vitalStatus.length > 0) - labels.push( - getLabelFromCriteriaObject(criteriaState, selectedCriteria.vitalStatus, CriteriaDataKey.VITALSTATUS, type) - ) - if ( - selectedCriteria.birthdates[0] === null && - selectedCriteria.birthdates[1] === null && - (selectedCriteria.age[0] !== null || selectedCriteria.age[1] !== null) - ) - labels.push(getDurationRangeLabel(selectedCriteria.age, 'Âge')) - if (selectedCriteria.birthdates[0] || selectedCriteria.birthdates[1]) - labels.push(getDatesLabel(selectedCriteria.birthdates, 'Naissance')) - if (selectedCriteria.deathDates[0] || selectedCriteria.deathDates[1]) - labels.push(getDatesLabel(selectedCriteria.deathDates, 'Décès')) - break - - case CriteriaType.ENCOUNTER: - if (selectedCriteria.age[0] || selectedCriteria.age[1]) - labels.push(getDurationRangeLabel(selectedCriteria.age, 'Âge : ')) - if (selectedCriteria.duration[0] || selectedCriteria.duration[1]) - labels.push(getDurationRangeLabel(selectedCriteria.duration, 'Prise en charge : ')) - if (selectedCriteria.priseEnChargeType?.length || 0 > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.priseEnChargeType, - CriteriaDataKey.PRISE_EN_CHARGE_TYPE, - type - ) - ) - if (selectedCriteria.typeDeSejour && selectedCriteria.typeDeSejour.length > 0) - labels.push( - getLabelFromCriteriaObject(criteriaState, selectedCriteria.typeDeSejour, CriteriaDataKey.TYPE_DE_SEJOUR, type) - ) - if (selectedCriteria.admissionMode && selectedCriteria.admissionMode.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.admissionMode, - CriteriaDataKey.ADMISSION_MODE, - type - ) - ) - if (selectedCriteria.admission && selectedCriteria.admission.length > 0) - labels.push( - getLabelFromCriteriaObject(criteriaState, selectedCriteria.admission, CriteriaDataKey.ADMISSION, type) - ) - if (selectedCriteria.entryMode && selectedCriteria.entryMode.length > 0) - labels.push( - getLabelFromCriteriaObject(criteriaState, selectedCriteria.entryMode, CriteriaDataKey.ENTRY_MODES, type) - ) - if (selectedCriteria.exitMode && selectedCriteria.exitMode.length > 0) - labels.push( - getLabelFromCriteriaObject(criteriaState, selectedCriteria.exitMode, CriteriaDataKey.EXIT_MODES, type) - ) - if (selectedCriteria.reason && selectedCriteria.reason.length > 0) - labels.push(getLabelFromCriteriaObject(criteriaState, selectedCriteria.reason, CriteriaDataKey.REASON, type)) - if (selectedCriteria.destination && selectedCriteria.destination.length > 0) - labels.push( - getLabelFromCriteriaObject(criteriaState, selectedCriteria.destination, CriteriaDataKey.DESTINATION, type) - ) - if (selectedCriteria.provenance && selectedCriteria.provenance.length > 0) - labels.push( - getLabelFromCriteriaObject(criteriaState, selectedCriteria.provenance, CriteriaDataKey.PROVENANCE, type) - ) - break - - case CriteriaType.DOCUMENTS: - if (selectedCriteria.search) - labels.push(getSearchDocumentLabel(selectedCriteria.search, selectedCriteria.searchBy)) - if (selectedCriteria.docType && selectedCriteria.docType.length > 0) - labels.push(getDocumentTypesLabel(selectedCriteria.docType)) - if (selectedCriteria.docStatuses && selectedCriteria.docStatuses.length > 0) { - labels.push(getDocumentStatusLabel(selectedCriteria.docStatuses)) - } - break - - case CriteriaType.CONDITION: - if (selectedCriteria.code && selectedCriteria.code.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.code, - CriteriaDataKey.CIM_10_DIAGNOSTIC, - selectedCriteria.type - ) - ) - if (selectedCriteria.diagnosticType && selectedCriteria.diagnosticType.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.diagnosticType, - CriteriaDataKey.DIAGNOSTIC_TYPES, - selectedCriteria.type - ) - ) - if (selectedCriteria.source) labels.push(`Source: ${selectedCriteria.source}`) - break - - case CriteriaType.PROCEDURE: - if (selectedCriteria.code && selectedCriteria.code.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.code, - CriteriaDataKey.CCAM_DATA, - selectedCriteria.type - ) - ) - if (selectedCriteria.source) labels.push(`Source: ${selectedCriteria.source}`) - break - - case CriteriaType.CLAIM: - if (selectedCriteria.code && selectedCriteria.code.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.code, - CriteriaDataKey.GHM_DATA, - selectedCriteria.type - ) - ) - break - - case CriteriaType.MEDICATION_REQUEST: - case CriteriaType.MEDICATION_ADMINISTRATION: - labels.push(getMedicationTypeLabel(selectedCriteria.type)) - if (selectedCriteria.code && selectedCriteria.code.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.code, - CriteriaDataKey.MEDICATION_DATA, - CriteriaType.MEDICATION - ) - ) - if ( - selectedCriteria.type === CriteriaType.MEDICATION_REQUEST && - selectedCriteria.prescriptionType && - selectedCriteria.prescriptionType.length > 0 - ) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.prescriptionType, - CriteriaDataKey.PRESCRIPTION_TYPES, - CriteriaType.MEDICATION - ) - ) - if (selectedCriteria.administration && selectedCriteria.administration.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.administration, - CriteriaDataKey.ADMINISTRATIONS, - CriteriaType.MEDICATION - ) - ) - break - - case CriteriaType.OBSERVATION: - if (selectedCriteria.code && selectedCriteria.code.length > 0) - labels.push( - getLabelFromCriteriaObject(criteriaState, selectedCriteria.code, CriteriaDataKey.BIOLOGY_DATA, type) - ) - if (selectedCriteria.valueComparator && selectedCriteria.searchByValue[0] !== null) - labels.push(getBiologyValuesLabel(selectedCriteria.valueComparator, selectedCriteria.searchByValue)) - break - - case CriteriaType.IMAGING: - if (selectedCriteria.studyStartDate || selectedCriteria.studyEndDate) - labels.push( - getDatesLabel([selectedCriteria.studyStartDate, selectedCriteria.studyEndDate], "Date de l'étude : ") - ) - if (selectedCriteria.studyModalities && selectedCriteria.studyModalities.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.studyModalities, - CriteriaDataKey.MODALITIES, - type, - "Modalités d'étude :" - ) - ) - if (selectedCriteria.studyDescription) - labels.push(`Description de l'étude : ${selectedCriteria.studyDescription}`) - if (selectedCriteria.studyProcedure) labels.push(`Code procédure de l'étude : ${selectedCriteria.studyProcedure}`) - if (selectedCriteria.withDocument !== DocumentAttachmentMethod.NONE) - labels.push(getAttachmentMethod(selectedCriteria.withDocument, selectedCriteria.daysOfDelay)) - if (selectedCriteria.studyUid) labels.push(getIdsListLabels(selectedCriteria.studyUid, "uuid d'étude")) - if (selectedCriteria.seriesStartDate || selectedCriteria.seriesEndDate) - labels.push( - getDatesLabel([selectedCriteria.seriesStartDate, selectedCriteria.seriesEndDate], 'Date de la série : ') - ) - if (selectedCriteria.seriesModalities && selectedCriteria.seriesModalities?.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.seriesModalities, - CriteriaDataKey.MODALITIES, - type, - 'Modalités de séries :' - ) - ) - if (selectedCriteria.seriesDescription) - labels.push(`Description de la série : ${selectedCriteria.seriesDescription}`) - if (selectedCriteria.seriesProtocol) labels.push(`Protocole de la série : ${selectedCriteria.seriesProtocol}`) - if (selectedCriteria.seriesUid) labels.push(getIdsListLabels(selectedCriteria.seriesUid, 'uuid de série')) - if (!isNaN(selectedCriteria.numberOfSeries) && selectedCriteria.seriesComparator) - labels.push( - getNbOccurencesLabel(selectedCriteria.numberOfSeries, selectedCriteria.seriesComparator, 'Nombre de séries') - ) - if (!isNaN(selectedCriteria.numberOfIns) && selectedCriteria.instancesComparator) - labels.push( - getNbOccurencesLabel(selectedCriteria.numberOfIns, selectedCriteria.instancesComparator, "Nombre d'instances") - ) - break - case CriteriaType.PREGNANCY: - if (selectedCriteria.pregnancyStartDate || selectedCriteria.pregnancyEndDate) - labels.push( - getDatesLabel( - [selectedCriteria.pregnancyStartDate, selectedCriteria.pregnancyEndDate], - 'Date de début de grossesse :' - ) - ) - if (selectedCriteria.pregnancyMode && selectedCriteria.pregnancyMode.length > 0) { - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.pregnancyMode, - CriteriaDataKey.PREGNANCY_MODE, - type, - 'Mode de grossesse :' - ) - ) - } - if (!isNaN(selectedCriteria.foetus) && selectedCriteria.foetusComparator && selectedCriteria.foetus !== 0) - labels.push( - getNbOccurencesLabel(selectedCriteria.foetus, selectedCriteria.foetusComparator, 'Nombre de foetus') - ) - if (!isNaN(selectedCriteria.parity) && selectedCriteria.parityComparator && selectedCriteria.parity !== 0) - labels.push(getNbOccurencesLabel(selectedCriteria.parity, selectedCriteria.parityComparator, 'Parité')) - if (selectedCriteria.maternalRisks && selectedCriteria.maternalRisks.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.maternalRisks, - CriteriaDataKey.MATERNAL_RISKS, - type, - 'Risques maternels :' - ) - ) - if (selectedCriteria.maternalRisksPrecision) - labels.push(`Précision sur les risques maternels : ${selectedCriteria.maternalRisksPrecision}`) - if (selectedCriteria.risksRelatedToObstetricHistory && selectedCriteria.risksRelatedToObstetricHistory.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.risksRelatedToObstetricHistory, - CriteriaDataKey.RISKS_RELATED_TO_OBSTETRIC_HISTORY, - type, - 'Risques liés aux antécédents obstétricaux :' - ) - ) - if (selectedCriteria.risksRelatedToObstetricHistoryPrecision) - labels.push( - `Précision sur les risques liés aux antécédents obstétricaux : ${selectedCriteria.risksRelatedToObstetricHistoryPrecision}` - ) - if ( - selectedCriteria.risksOrComplicationsOfPregnancy && - selectedCriteria.risksOrComplicationsOfPregnancy.length > 0 - ) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.risksOrComplicationsOfPregnancy, - CriteriaDataKey.RISKS_OR_COMPLICATIONS_OF_PREGNANCY, - type, - 'Risques ou complications de la grossesse :' - ) - ) - if (selectedCriteria.risksOrComplicationsOfPregnancyPrecision) - labels.push( - `Précision sur les risques ou complications de la grossesse : ${selectedCriteria.risksOrComplicationsOfPregnancyPrecision}` - ) - if (selectedCriteria.corticotherapie && selectedCriteria.corticotherapie.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.corticotherapie, - CriteriaDataKey.CORTICOTHERAPIE, - type, - 'Corticothérapie :' - ) - ) - if (selectedCriteria.prenatalDiagnosis && selectedCriteria.prenatalDiagnosis.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.prenatalDiagnosis, - CriteriaDataKey.PRENATAL_DIAGNOSIS, - type, - 'Diagnostic prénatal :' - ) - ) - if (selectedCriteria.ultrasoundMonitoring && selectedCriteria.ultrasoundMonitoring.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.ultrasoundMonitoring, - CriteriaDataKey.ULTRASOUND_MONITORING, - type, - 'Suivi échographique :' - ) - ) - break - case CriteriaType.HOSPIT: - if (selectedCriteria.hospitReason) labels.push(`Motif(s) d'hospitalisation : ${selectedCriteria.hospitReason}`) - if (selectedCriteria.inUteroTransfer && selectedCriteria.inUteroTransfer.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.inUteroTransfer, - CriteriaDataKey.IN_UTERO_TRANSFER, - type, - 'Transfert in utero :' - ) - ) - if (selectedCriteria.pregnancyMonitoring && selectedCriteria.pregnancyMonitoring.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.pregnancyMonitoring, - CriteriaDataKey.PREGNANCY_MONITORING, - type, - 'Grossesse peu ou pas suivie :' - ) - ) - if (selectedCriteria.vme && selectedCriteria.vme.length > 0) - labels.push(getLabelFromCriteriaObject(criteriaState, selectedCriteria.vme, CriteriaDataKey.VME, type, 'VME :')) - if (selectedCriteria.maturationCorticotherapie && selectedCriteria.maturationCorticotherapie.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.maturationCorticotherapie, - CriteriaDataKey.MATURATION_CORTICOTHERAPIE, - type, - 'Corticothérapie pour maturation foetal faite :' - ) - ) - if (selectedCriteria.chirurgicalGesture && selectedCriteria.chirurgicalGesture.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.chirurgicalGesture, - CriteriaDataKey.CHIRURGICAL_GESTURE, - type, - 'Type de geste ou de chirurgie :' - ) - ) - if (selectedCriteria.childbirth && selectedCriteria.childbirth.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.childbirth, - CriteriaDataKey.CHILDBIRTH, - type, - 'Accouchement :' - ) - ) - if (selectedCriteria.hospitalChildBirthPlace && selectedCriteria.hospitalChildBirthPlace.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.hospitalChildBirthPlace, - CriteriaDataKey.HOSPITALCHILDBIRTHPLACE, - type, - "Accouchement à la maternité de l'hospitalisation :" - ) - ) - if (selectedCriteria.otherHospitalChildBirthPlace && selectedCriteria.otherHospitalChildBirthPlace.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.otherHospitalChildBirthPlace, - CriteriaDataKey.OTHERHOSPITALCHILDBIRTHPLACE, - type, - 'Accouchement dans un autre hôpital :' - ) - ) - if (selectedCriteria.homeChildBirthPlace && selectedCriteria.homeChildBirthPlace.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.homeChildBirthPlace, - CriteriaDataKey.HOMECHILDBIRTHPLACE, - type, - 'Accouchement à domicile :' - ) - ) - if (selectedCriteria.childbirthMode && selectedCriteria.childbirthMode.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.childbirthMode, - CriteriaDataKey.CHILDBIRTH_MODE, - type, - 'Mode de mise en travail :' - ) - ) - if (selectedCriteria.maturationReason && selectedCriteria.maturationReason.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.maturationReason, - CriteriaDataKey.MATURATION_REASON, - type, - 'Motif(s) de maturation / déclenchement :' - ) - ) - if (selectedCriteria.maturationModality && selectedCriteria.maturationModality.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.maturationModality, - CriteriaDataKey.MATURATION_MODALITY, - type, - 'Modalités de maturation cervicale initiale :' - ) - ) - if (selectedCriteria.imgIndication && selectedCriteria.imgIndication.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.imgIndication, - CriteriaDataKey.IMG_INDICATION, - type, - "Indication de l'IMG :" - ) - ) - if (selectedCriteria.laborOrCesareanEntry && selectedCriteria.laborOrCesareanEntry.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.laborOrCesareanEntry, - CriteriaDataKey.LABOR_OR_CESAREAN_ENTRY, - type, - "Présentation à l'entrée en travail ou en début de césarienne :" - ) - ) - if (selectedCriteria.pathologyDuringLabor && selectedCriteria.pathologyDuringLabor.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.pathologyDuringLabor, - CriteriaDataKey.PATHOLOGY_DURING_LABOR, - type, - 'Pathologie pendant le travail :' - ) - ) - if (selectedCriteria.obstetricalGestureDuringLabor && selectedCriteria.obstetricalGestureDuringLabor.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.obstetricalGestureDuringLabor, - CriteriaDataKey.OBSTETRICAL_GESTURE_DURING_LABOR, - type, - 'Geste ou manoeuvre obstétricale pendant le travail :' - ) - ) - if (selectedCriteria.analgesieType && selectedCriteria.analgesieType.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.analgesieType, - CriteriaDataKey.ANALGESIE_TYPE, - type, - 'ANALGESIE / ANESTHESIE - type :' - ) - ) - if (selectedCriteria.birthDeliveryStartDate || selectedCriteria.birthDeliveryEndDate) - labels.push( - getDatesLabel( - [selectedCriteria.birthDeliveryStartDate, selectedCriteria.birthDeliveryEndDate], - "Date/heure de l'accouchement :" - ) - ) - if ( - !isNaN(selectedCriteria.birthDeliveryWeeks) && - selectedCriteria.birthDeliveryWeeksComparator && - selectedCriteria.birthDeliveryWeeks !== 0 - ) - labels.push( - getNbOccurencesLabel( - selectedCriteria.birthDeliveryWeeks, - selectedCriteria.birthDeliveryWeeksComparator, - 'Nombre de semaines (Accouchement - Terme)' - ) - ) - if ( - !isNaN(selectedCriteria.birthDeliveryDays) && - selectedCriteria.birthDeliveryDaysComparator && - selectedCriteria.birthDeliveryDays !== 0 - ) - labels.push( - getNbOccurencesLabel( - selectedCriteria.birthDeliveryDays, - selectedCriteria.birthDeliveryDaysComparator, - 'Nombre de jours (Accouchement - Terme)' - ) - ) - if (selectedCriteria.birthDeliveryWay && selectedCriteria.birthDeliveryWay.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.birthDeliveryWay, - CriteriaDataKey.BIRTH_DELIVERY_WAY, - type, - "Voie d'accouchement :" - ) - ) - if (selectedCriteria.instrumentType && selectedCriteria.instrumentType.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.instrumentType, - CriteriaDataKey.INSTRUMENT_TYPE, - type, - "Type d'instrument :" - ) - ) - if (selectedCriteria.cSectionModality && selectedCriteria.cSectionModality.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.cSectionModality, - CriteriaDataKey.C_SECTION_MODALITY, - type, - 'Modalités de la césarienne :' - ) - ) - if (selectedCriteria.presentationAtDelivery && selectedCriteria.presentationAtDelivery.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.presentationAtDelivery, - CriteriaDataKey.PRESENTATION_AT_DELIVERY, - type, - "Présentation à l'accouchement :" - ) - ) - if ( - !isNaN(selectedCriteria.birthMensurationsGrams) && - selectedCriteria.birthMensurationsGramsComparator && - selectedCriteria.birthMensurationsGrams !== 0 - ) - labels.push( - getNbOccurencesLabel( - selectedCriteria.birthMensurationsGrams, - selectedCriteria.birthMensurationsGramsComparator, - 'Mensurations naissance - Poids (g) :' - ) - ) - if ( - !isNaN(selectedCriteria.birthMensurationsPercentil) && - selectedCriteria.birthMensurationsPercentilComparator && - selectedCriteria.birthMensurationsPercentil !== 0 - ) - labels.push( - getNbOccurencesLabel( - selectedCriteria.birthMensurationsPercentil, - selectedCriteria.birthMensurationsPercentilComparator, - 'Mensurations naissance - Poids (percentile) :' - ) - ) - if (!isNaN(selectedCriteria.apgar1) && selectedCriteria.apgar1Comparator && selectedCriteria.apgar1 !== 0) - labels.push( - getNbOccurencesLabel(selectedCriteria.apgar1, selectedCriteria.apgar1Comparator, 'Score Apgar - 1 min :') - ) - if (!isNaN(selectedCriteria.apgar3) && selectedCriteria.apgar3Comparator && selectedCriteria.apgar3 !== 0) - labels.push( - getNbOccurencesLabel(selectedCriteria.apgar3, selectedCriteria.apgar3Comparator, 'Score Apgar - 3 min :') - ) - if (!isNaN(selectedCriteria.apgar5) && selectedCriteria.apgar5Comparator && selectedCriteria.apgar5 !== 0) - labels.push( - getNbOccurencesLabel(selectedCriteria.apgar5, selectedCriteria.apgar5Comparator, 'Score Apgar - 5 min :') - ) - if (!isNaN(selectedCriteria.apgar10) && selectedCriteria.apgar10Comparator && selectedCriteria.apgar10 !== 0) - labels.push( - getNbOccurencesLabel(selectedCriteria.apgar10, selectedCriteria.apgar10Comparator, 'Score Apgar - 10 min :') - ) - if ( - !isNaN(selectedCriteria.arterialPhCord) && - selectedCriteria.arterialPhCordComparator && - selectedCriteria.arterialPhCord !== 0 - ) - labels.push( - getNbOccurencesLabel( - selectedCriteria.arterialPhCord, - selectedCriteria.arterialPhCordComparator, - 'pH artériel au cordon :' - ) - ) - if ( - !isNaN(selectedCriteria.arterialCordLactates) && - selectedCriteria.arterialCordLactatesComparator && - selectedCriteria.arterialCordLactates !== 0 - ) - labels.push( - getNbOccurencesLabel( - selectedCriteria.arterialCordLactates, - selectedCriteria.arterialCordLactatesComparator, - 'Lactate artériel au cordon (mmol/L) :' - ) - ) - if (selectedCriteria.birthStatus && selectedCriteria.birthStatus.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.birthStatus, - CriteriaDataKey.BIRTHSTATUS, - type, - 'Statut vital à la naissance :' - ) - ) - if (selectedCriteria.postpartumHemorrhage && selectedCriteria.postpartumHemorrhage.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.postpartumHemorrhage, - CriteriaDataKey.POSTPARTUM_HEMORRHAGE, - type, - 'Hémorragie du post-partum :' - ) - ) - if (selectedCriteria.conditionPerineum && selectedCriteria.conditionPerineum.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.conditionPerineum, - CriteriaDataKey.CONDITION_PERINEUM, - type, - 'État du Périnée :' - ) - ) - if (selectedCriteria.exitPlaceType && selectedCriteria.exitPlaceType.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.exitPlaceType, - CriteriaDataKey.EXIT_PLACE_TYPE, - type, - 'Type de lieu de sortie :' - ) - ) - if (selectedCriteria.feedingType && selectedCriteria.feedingType.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.feedingType, - CriteriaDataKey.FEEDING_TYPE, - type, - "Type d'allaitement :" - ) - ) - if (selectedCriteria.complication && selectedCriteria.complication.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.complication, - CriteriaDataKey.COMPLICATION, - type, - 'Aucune complication :' - ) - ) - if (selectedCriteria.exitFeedingMode && selectedCriteria.exitFeedingMode.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.exitFeedingMode, - CriteriaDataKey.EXIT_FEEDING_MODE, - type, - "Mode d'allaitement à la sortie :" - ) - ) - if (selectedCriteria.exitDiagnostic && selectedCriteria.exitDiagnostic.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.exitDiagnostic, - CriteriaDataKey.EXIT_DIAGNOSTIC, - type, - 'Diagnostic de sortie :' - ) - ) - } - switch (type) { - case CriteriaType.DOCUMENTS: - case CriteriaType.CONDITION: - case CriteriaType.CLAIM: - case CriteriaType.PROCEDURE: - case CriteriaType.MEDICATION_REQUEST: - case CriteriaType.MEDICATION_ADMINISTRATION: - case CriteriaType.OBSERVATION: - case CriteriaType.ENCOUNTER: - case CriteriaType.IMAGING: - case CriteriaType.PREGNANCY: - case CriteriaType.HOSPIT: - if (type !== CriteriaType.PREGNANCY && type !== CriteriaType.HOSPIT) { - if (selectedCriteria.encounterStartDate[0] !== null || selectedCriteria.encounterStartDate[1] !== null) - labels.push( - getDatesLabel( - selectedCriteria.encounterStartDate, - 'Date de début de prise en charge', - selectedCriteria.includeEncounterStartDateNull - ) - ) - if (selectedCriteria.encounterEndDate[0] !== null || selectedCriteria.encounterEndDate[1] !== null) - labels.push( - getDatesLabel( - selectedCriteria.encounterEndDate, - 'Date de fin de prise en charge', - selectedCriteria.includeEncounterEndDateNull - ) - ) - } - if (selectedCriteria.occurrence && !isNaN(selectedCriteria.occurrence) && selectedCriteria.occurrenceComparator) - labels.push( - getNbOccurencesLabel( - selectedCriteria.occurrence, - selectedCriteria.occurrenceComparator, - "Nombre d'occurrences" - ) - ) - if (selectedCriteria.startOccurrence?.[0] !== null || selectedCriteria.startOccurrence?.[1] !== null) - labels.push( - getDatesLabel( - selectedCriteria.startOccurrence, - getOccurenceDateLabel( - selectedCriteria.type as Exclude - ) - ) - ) - if ( - selectedCriteria.endOccurrence && - (selectedCriteria.endOccurrence?.[0] !== null || selectedCriteria.endOccurrence?.[1] !== null) - ) - labels.push( - getDatesLabel( - selectedCriteria.endOccurrence ?? [null, null], - getOccurenceDateLabel( - selectedCriteria.type as Exclude, - true - ) - ) - ) - if (selectedCriteria.encounterService && selectedCriteria.encounterService.length > 0) - labels.push(getLabelFromName(selectedCriteria.encounterService)) - if (selectedCriteria.encounterStatus && selectedCriteria.encounterStatus.length > 0) - labels.push( - getLabelFromCriteriaObject( - criteriaState, - selectedCriteria.encounterStatus, - CriteriaDataKey.ENCOUNTER_STATUS, - type === CriteriaType.MEDICATION_ADMINISTRATION || type === CriteriaType.MEDICATION_REQUEST - ? CriteriaType.MEDICATION - : type, - `Statut de la visite ${!(type === CriteriaType.ENCOUNTER) ? 'associée ' : ''}:` - ) - ) - } - - return labels -}