diff --git a/.gitignore b/.gitignore index 349500c11..ca459b0bf 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,7 @@ yarn-error.log* .env* !.env.example .mp4 -public/config.dev.json \ No newline at end of file +public/config.dev.json +public/config.example.json +public/config.qualif.json +*storybook.log 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 7a5da10ad..8ad103339 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", @@ -2912,788 +2983,604 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { + "node_modules/@esbuild/linux-x64": { "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ - "ppc64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "aix" + "linux" ], "engines": { "node": ">=12" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=12" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=12" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@floating-ui/core": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz", + "integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@floating-ui/utils": "^0.2.7" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@floating-ui/dom": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.10.tgz", + "integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.7" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@floating-ui/utils": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz", + "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": ">=12" + "node": ">=10.10.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { "node": ">=12" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@isaacs/cliui/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==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "ansi-regex": "^6.0.1" + }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "peer": true, "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "license": "MIT", + "peer": true, "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "p-locate": "^4.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=8" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "license": "MIT", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "license": "MIT", + "peer": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "p-limit": "^2.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, + "peer": true, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "license": "(MIT OR CC0-1.0)", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "node_modules/@jest/console": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@floating-ui/core": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz", - "integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==", + "node_modules/@jest/console/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", "license": "MIT", + "peer": true, "dependencies": { - "@floating-ui/utils": "^0.2.7" + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@floating-ui/dom": { - "version": "1.6.10", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.10.tgz", - "integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==", + "node_modules/@jest/console/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", "license": "MIT", + "peer": true, "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.7" + "@types/yargs-parser": "*" } }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", - "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "node_modules/@jest/console/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==", "license": "MIT", + "peer": true, "dependencies": { - "@floating-ui/dom": "^1.0.0" + "color-convert": "^2.0.1" }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@floating-ui/utils": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz", - "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==", - "license": "MIT" - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", - "license": "Apache-2.0", + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" + "node": ">=10" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "license": "BSD-3-Clause" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", + "node_modules/@jest/console/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==", + "license": "MIT", + "peer": true, "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=12" + "node": ">=7.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/@jest/console/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==", "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } + "peer": true }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "node_modules/@jest/console/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==", "license": "MIT", + "peer": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=8" } }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@jest/console/node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", "license": "MIT", + "peer": true, "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@isaacs/cliui/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==", + "node_modules/@jest/console/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", "license": "MIT", + "peer": true, "dependencies": { - "ansi-regex": "^6.0.1" + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "license": "ISC", - "peer": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "license": "MIT", - "peer": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/@jest/console/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==", "license": "MIT", "peer": true, "dependencies": { - "p-limit": "^2.2.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { + "node_modules/@jest/core": { "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", "license": "MIT", "peer": true, "dependencies": { + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", "@jest/types": "^27.5.1", "@types/node": "*", + "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", "jest-util": "^27.5.1", - "slash": "^3.0.0" + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@jest/console/node_modules/@jest/types": { + "node_modules/@jest/core/node_modules/@jest/types": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", @@ -3710,7 +3597,7 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@jest/console/node_modules/@types/yargs": { + "node_modules/@jest/core/node_modules/@types/yargs": { "version": "16.0.9", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", @@ -3720,7 +3607,7 @@ "@types/yargs-parser": "*" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { + "node_modules/@jest/core/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==", @@ -3736,7 +3623,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/console/node_modules/chalk": { + "node_modules/@jest/core/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -3753,7 +3640,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/console/node_modules/color-convert": { + "node_modules/@jest/core/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==", @@ -3766,14 +3653,14 @@ "node": ">=7.0.0" } }, - "node_modules/@jest/console/node_modules/color-name": { + "node_modules/@jest/core/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==", "license": "MIT", "peer": true }, - "node_modules/@jest/console/node_modules/has-flag": { + "node_modules/@jest/core/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==", @@ -3783,7 +3670,7 @@ "node": ">=8" } }, - "node_modules/@jest/console/node_modules/jest-message-util": { + "node_modules/@jest/core/node_modules/jest-message-util": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", @@ -3804,7 +3691,7 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@jest/console/node_modules/jest-util": { + "node_modules/@jest/core/node_modules/jest-util": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", @@ -3822,7 +3709,24 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/@jest/console/node_modules/supports-color": { + "node_modules/@jest/core/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/core/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==", @@ -3835,230 +3739,23 @@ "node": ">=8" } }, - "node_modules/@jest/core": { + "node_modules/@jest/environment": { "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", - "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", "license": "MIT", "peer": true, "dependencies": { - "@jest/console": "^27.5.1", - "@jest/reporters": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", + "@jest/fake-timers": "^27.5.1", "@jest/types": "^27.5.1", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^27.5.1", - "jest-config": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-resolve-dependencies": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "jest-watcher": "^27.5.1", - "micromatch": "^4.0.4", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "jest-mock": "^27.5.1" }, "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } } }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "16.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", - "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/core/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==", - "license": "MIT", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "peer": 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/@jest/core/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==", - "license": "MIT", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/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==", - "license": "MIT", - "peer": true - }, - "node_modules/@jest/core/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==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/core/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==", - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@jest/types": { + "node_modules/@jest/environment/node_modules/@jest/types": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", @@ -5149,6 +4846,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 +5009,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", @@ -5860,77 +5607,25 @@ } } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", - "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", - "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", - "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { + "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", - "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", "cpu": [ "x64" ], "dev": true, "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", - "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", - "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", "cpu": [ - "arm" + "x64" ], "dev": true, "optional": true, @@ -5938,173 +5633,917 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", - "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "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/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", - "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", - "cpu": [ - "arm64" - ], + "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, - "optional": true, - "os": [ - "linux" - ] + "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/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", - "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", - "cpu": [ - "ppc64" - ], + "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, - "optional": true, - "os": [ - "linux" - ] + "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/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", - "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", - "cpu": [ - "riscv64" - ], + "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, - "optional": true, - "os": [ - "linux" - ] + "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/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", - "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", - "cpu": [ - "s390x" - ], + "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, - "optional": true, - "os": [ - "linux" - ] + "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/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", - "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", - "cpu": [ - "x64" - ], + "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, - "optional": true, - "os": [ - "linux" - ] + "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/@rollup/rollup-linux-x64-musl": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", - "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", - "cpu": [ - "x64" - ], + "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, - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", - "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", - "cpu": [ - "arm64" - ], + "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, - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", - "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", - "cpu": [ - "ia32" - ], + "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, - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "dequal": "^2.0.3" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", - "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", - "cpu": [ - "x64" - ], + "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, - "optional": true, - "os": [ - "win32" - ] + "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/@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/@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": { @@ -6387,106 +6826,21 @@ "@swc/core-darwin-arm64": "1.7.14", "@swc/core-darwin-x64": "1.7.14", "@swc/core-linux-arm-gnueabihf": "1.7.14", - "@swc/core-linux-arm64-gnu": "1.7.14", - "@swc/core-linux-arm64-musl": "1.7.14", - "@swc/core-linux-x64-gnu": "1.7.14", - "@swc/core-linux-x64-musl": "1.7.14", - "@swc/core-win32-arm64-msvc": "1.7.14", - "@swc/core-win32-ia32-msvc": "1.7.14", - "@swc/core-win32-x64-msvc": "1.7.14" - }, - "peerDependencies": { - "@swc/helpers": "*" - }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } - } - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.7.14", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.14.tgz", - "integrity": "sha512-V0OUXjOH+hdGxDYG8NkQzy25mKOpcNKFpqtZEzLe5V/CpLJPnpg1+pMz70m14s9ZFda9OxsjlvPbg1FLUwhgIQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.7.14", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.14.tgz", - "integrity": "sha512-9iFvUnxG6FC3An5ogp5jbBfQuUmTTwy8KMB+ZddUoPB3NR1eV+Y9vOh/tfWcenSJbgOKDLgYC5D/b1mHAprsrQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.7.14", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.14.tgz", - "integrity": "sha512-zGJsef9qPivKSH8Vv4F/HiBXBTHZ5Hs3ZjVGo/UIdWPJF8fTL9OVADiRrl34Q7zOZEtGXRwEKLUW1SCQcbDvZA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.7.14", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.14.tgz", - "integrity": "sha512-AxV3MPsoI7i4B8FXOew3dx3N8y00YoJYvIPfxelw07RegeCEH3aHp2U2DtgbP/NV1ugZMx0TL2Z2DEvocmA51g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.7.14", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.14.tgz", - "integrity": "sha512-JDLdNjUj3zPehd4+DrQD8Ltb3B5lD8D05IwePyDWw+uR/YPc7w/TX1FUVci5h3giJnlMCJRvi1IQYV7K1n7KtQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" + "@swc/core-linux-arm64-gnu": "1.7.14", + "@swc/core-linux-arm64-musl": "1.7.14", + "@swc/core-linux-x64-gnu": "1.7.14", + "@swc/core-linux-x64-musl": "1.7.14", + "@swc/core-win32-arm64-msvc": "1.7.14", + "@swc/core-win32-ia32-msvc": "1.7.14", + "@swc/core-win32-x64-msvc": "1.7.14" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } } }, "node_modules/@swc/core-linux-x64-gnu": { @@ -6523,57 +6877,6 @@ "node": ">=10" } }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.7.14", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.14.tgz", - "integrity": "sha512-Jp8KDlfq7Ntt2/BXr0y344cYgB1zf0DaLzDZ1ZJR6rYlAzWYSccLYcxHa97VGnsYhhPspMpmCvHid97oe2hl4A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.7.14", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.14.tgz", - "integrity": "sha512-I+cFsXF0OU0J9J4zdWiQKKLURO5dvCujH9Jr8N0cErdy54l9d4gfIxdctfTF+7FyXtWKLTCkp+oby9BQhkFGWA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.7.14", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.14.tgz", - "integrity": "sha512-NNrprQCK6d28mG436jVo2TD+vACHseUECacEBGZ9Ef0qfOIWS1XIt2MisQKG0Oea2VvLFl6tF/V4Lnx/H0Sn3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -6889,7 +7192,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 +7212,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 +7528,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 +7561,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 +7588,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 +7600,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 +7614,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 +7627,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 +7647,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 +7697,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 +7802,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 +7869,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 +7884,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 +8039,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 +8059,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 +8103,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 +8722,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 +8796,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 +9060,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 +9244,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 +9795,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 +9866,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 +9889,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 +9897,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 +9905,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 +9915,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 +9972,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 +10330,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 +10549,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 +10642,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 +10653,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" } @@ -10274,10 +10664,10 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "peer": true, + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10286,8 +10676,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", @@ -10351,9 +10740,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -11508,7 +11898,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" } @@ -11559,7 +11948,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" } @@ -11577,7 +11965,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" @@ -11909,8 +12296,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", @@ -11967,7 +12353,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" } @@ -12154,8 +12539,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", @@ -12248,6 +12632,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", @@ -12261,8 +12657,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", @@ -12281,7 +12676,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", @@ -12304,7 +12698,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", "optional": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12784,6 +13177,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", @@ -13062,7 +13482,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" @@ -13124,7 +13543,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" } @@ -13197,17 +13615,16 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "peer": true, + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -13221,7 +13638,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -13236,10 +13653,6 @@ }, "engines": { "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -13247,7 +13660,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" } @@ -13256,8 +13668,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", @@ -13469,7 +13880,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", @@ -13487,7 +13897,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" } @@ -13495,15 +13904,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", @@ -13521,7 +13928,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" }, @@ -13537,7 +13943,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" } @@ -13854,7 +14259,6 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -13877,7 +14281,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" } @@ -13943,20 +14346,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -14110,6 +14499,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", @@ -14143,6 +14538,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", @@ -14394,6 +14808,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", @@ -14623,10 +15076,22 @@ "domelementtype": "^2.3.0" }, "engines": { - "node": ">= 4" + "node": ">= 4" + }, + "funding": { + "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/fb55/domhandler?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/html-webpack-plugin": { @@ -14719,7 +15184,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", @@ -14769,9 +15233,10 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", - "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "license": "MIT", "peer": true, "dependencies": { "@types/http-proxy": "^1.17.8", @@ -15023,6 +15488,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", @@ -15178,7 +15655,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" }, @@ -15343,6 +15819,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", @@ -15523,7 +16008,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" }, @@ -19440,6 +19924,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", @@ -20002,6 +20495,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", @@ -20013,7 +20524,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" } @@ -20031,11 +20541,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" } @@ -20077,7 +20595,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" } @@ -20104,7 +20621,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" }, @@ -20373,7 +20889,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" } @@ -20710,7 +21225,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" }, @@ -20758,7 +21272,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", @@ -20837,7 +21350,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" } @@ -20901,7 +21413,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" } @@ -20973,10 +21484,9 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "peer": true + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", @@ -21077,7 +21587,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" }, @@ -21090,7 +21599,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" @@ -21104,7 +21612,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" }, @@ -21117,7 +21624,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" }, @@ -21133,7 +21639,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" }, @@ -21220,6 +21725,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", @@ -22769,6 +23286,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", @@ -22822,7 +23348,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" @@ -22836,7 +23361,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" } @@ -22878,7 +23402,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" }, @@ -22957,7 +23480,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" } @@ -22966,7 +23488,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", @@ -22981,7 +23502,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" } @@ -22990,7 +23510,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" }, @@ -23076,6 +23595,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", @@ -23198,6 +23742,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", @@ -23211,6 +23806,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", @@ -23521,6 +24137,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", @@ -23776,6 +24417,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", @@ -23909,6 +24585,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", @@ -24361,7 +25046,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", @@ -24385,7 +25069,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" } @@ -24393,14 +25076,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" } @@ -24408,8 +25089,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", @@ -24511,7 +25191,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", @@ -24575,8 +25254,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", @@ -24813,6 +25491,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", @@ -25007,7 +25695,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" } @@ -25031,6 +25718,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", @@ -25725,6 +26430,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", @@ -26069,7 +26783,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" } @@ -26122,6 +26835,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", @@ -26226,6 +26948,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", @@ -26265,7 +26993,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" @@ -26464,6 +27191,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", @@ -26487,11 +27256,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", @@ -26568,6 +27357,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", @@ -26602,7 +27404,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" } @@ -26650,7 +27451,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" } @@ -27630,6 +28430,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 4eb77ac53..1294fa83f 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..9e3047919 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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).join("&")) }) it('should return default Medication administation criteria', () => { const selectedCriteria: MedicationDataType = completeMedicationPrescriptionCriteria @@ -400,21 +419,21 @@ 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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).join("&")) }) it('should return build complete obervation criteria', () => { const selectedCriteria: ObservationDataType = completeObservationCriteria const result = [ - 'subject.active=true&status=Val', - 'code=I3356', + 'status=Val&subject.active=true', + 'code=https://terminology.eds.aphp.fr/aphp-itm-anabio|I3356', 'encounter.encounter-care-site=8312016825', 'encounter.status=cancelled', 'date=ge2024-09-03T00:00:00Z', @@ -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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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((a, b) => a.localeCompare(b)).join("&")).toEqual(result.filter(el => !!el).sort((a, b) => a.localeCompare(b)).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..6a3fe05bc 100644 --- a/src/__tests__/data/cohortCreation/claimCriteria.ts +++ b/src/__tests__/data/cohortCreation/claimCriteria.ts @@ -1,34 +1,21 @@ -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, - encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], + 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: ['cancelled'], code: [ { id: '05C021', diff --git a/src/__tests__/data/cohortCreation/cohortCreation.ts b/src/__tests__/data/cohortCreation/cohortCreation.ts index 14298159b..46b09953a 100644 --- a/src/__tests__/data/cohortCreation/cohortCreation.ts +++ b/src/__tests__/data/cohortCreation/cohortCreation.ts @@ -1,87 +1,48 @@ -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'], - encounterStatus: [ - { - id: 'cancelled', - label: 'Cancelled', - system: 'http://hl7.org/fhir/CodeSystem/encounter-status' - } - ], - encounterEndDate: ['2024-08-22', '2024-08-22'], + startOccurrence: { start: '2024-08-15', end: '2024-08-22' }, + encounterStartDate: { start: '2024-08-07', end: '2024-08-21' }, + encounterStatus: ['cancelled'], + encounterEndDate: { start: '2024-08-22', end: '2024-08-22' }, code: [ { id: '000212', @@ -102,53 +63,43 @@ export const procedurePeudonimizedCriteria: SelectedCriteriaType[] = [ export const patientPseudonimizedCriteria: SelectedCriteriaType[] = [ { ...defaultPatientCriteria, - genders: [ - { - id: 'f', - label: 'Femme' - } - ], - vitalStatus: [ - { - id: 'alive', - label: 'Vivant' - } - ] + genders: ['f'], + vitalStatus: ['alive'] } ] 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' } } ] @@ -156,19 +107,9 @@ export const criteriasArrayWtihNominativeData: SelectedCriteriaType[] = [ ...procedurePeudonimizedCriteria, { ...defaultPatientCriteria, - genders: [ - { - id: 'f', - label: 'Femme' - } - ], - vitalStatus: [ - { - id: 'alive', - label: 'Vivant' - } - ], - deathDates: ['2024-08-15', '2024-08-15'] + genders: ['f'], + vitalStatus: ['true'], + deathDates: { start: '2024-08-15', end: '2024-08-15' } } ] @@ -176,19 +117,9 @@ export const criteriaArrayWithNoNominativeData: SelectedCriteriaType[] = [ ...procedurePeudonimizedCriteria, { ...defaultPatientCriteria, - genders: [ - { - id: 'f', - label: 'Femme' - } - ], - vitalStatus: [ - { - id: 'alive', - label: 'Vivant' - } - ], - age: ['0/1/2', '0/5/15'] + genders: ['f'], + vitalStatus: ['false'], + age: { start: '0/1/2', end: '0/5/15' } } ] @@ -214,13 +145,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..000a0d45b 100644 --- a/src/__tests__/data/cohortCreation/conditionCriteria.ts +++ b/src/__tests__/data/cohortCreation/conditionCriteria.ts @@ -1,35 +1,22 @@ -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, - encounterStatus: [{ id: 'finished', label: 'Finished', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], + 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: ['finished'], code: [ { id: 'I841', @@ -43,18 +30,7 @@ export const completeConditionCriteria: Cim10DataType = { } ], source: 'AREM', - diagnosticType: [ - { - id: 'fp', - label: 'fp - Finalité Principale De Prise En Charge', - system: 'https://terminology.eds.aphp.fr/aphp-orbis-condition-status' - }, - { - id: 'f', - label: 'f - Finalité De Prise En Charge', - system: 'https://terminology.eds.aphp.fr/aphp-orbis-condition-status' - } - ], + diagnosticType: ['fp', 'f'], encounterService: [ { above_levels_ids: '8312002244', diff --git a/src/__tests__/data/cohortCreation/documentCriteria.ts b/src/__tests__/data/cohortCreation/documentCriteria.ts index e5b80a2ae..293b095d7 100644 --- a/src/__tests__/data/cohortCreation/documentCriteria.ts +++ b/src/__tests__/data/cohortCreation/documentCriteria.ts @@ -1,43 +1,24 @@ -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, 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, - encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], - docStatuses: ['Validé', 'Non validé'], - docType: [ - { type: 'Comptes Rendus Hospitalisation', label: 'CR de Jour', code: 'crh-j' }, - { type: 'Comptes Rendus Hospitalisation', label: 'CRH Chirurgie', code: 'crh-chir' } - ], + 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: ['cancelled'], + docStatuses: [DocumentStatuses.FINAL, DocumentStatuses.PRELIMINARY], + docType: ['crh-j', 'crh-chir'], search: 'cancer', searchBy: SearchByTypes.TEXT, encounterService: [ diff --git a/src/__tests__/data/cohortCreation/encounterCriteria.ts b/src/__tests__/data/cohortCreation/encounterCriteria.ts index f8b12c30a..ed85bcdde 100644 --- a/src/__tests__/data/cohortCreation/encounterCriteria.ts +++ b/src/__tests__/data/cohortCreation/encounterCriteria.ts @@ -1,145 +1,32 @@ -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, - encounterStatus: [ - { - id: 'status1', - label: 'status1' - }, - { - id: 'status2', - label: 'status2' - } - ], - age: ['3/2/1', '3/2/1'], - duration: ['6/5/4', '6/5/4'], - admissionMode: [ - { - id: 'mode1', - label: 'mode1' - }, - { - id: 'mode2', - label: 'mode2' - } - ], - entryMode: [ - { - id: 'entry1', - label: 'entry1' - }, - { - id: 'entry2', - label: 'entry2' - } - ], - exitMode: [ - { - id: 'exit1', - label: 'exit1' - }, - { - id: 'exit2', - label: 'exit2' - } - ], - priseEnChargeType: [ - { - id: 'prise1', - label: 'prise1' - }, - { - id: 'prise2', - label: 'prise2' - } - ], - typeDeSejour: [ - { - id: 'sejour1', - label: 'sejour1' - }, - { - id: 'sejour2', - label: 'sejour2' - } - ], - reason: [ - { - id: 'reason1', - label: 'reason1' - }, - { - id: 'reason2', - label: 'reason2' - } - ], - destination: [ - { - id: 'destination1', - label: 'destination1' - }, - { - id: 'destination2', - label: 'destination2' - } - ], - provenance: [ - { - id: 'provenance1', - label: 'provenance1' - }, - { - id: 'provenance2', - label: 'provenance2' - } - ], - admission: [ - { - id: 'admission1', - label: 'admission1' - }, - { - id: 'admission2', - label: 'admission2' - } - ], + 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: ['status1', 'status2'], + age: { start: '3/2/1', end: '3/2/1' }, + duration: { start: '6/5/4', end: '6/5/4' }, + admissionMode: ['mode1', 'mode2'], + entryMode: ['entry1', 'entry2'], + exitMode: ['exit1', 'exit2'], + priseEnChargeType: ['prise1', 'prise2'], + typeDeSejour: ['sejour1', 'sejour2'], + reason: ['reason1', 'reason2'], + destination: ['destination1', 'destination2'], + provenance: ['provenance1', 'provenance2'], + admission: ['admission1', 'admission2'], encounterService: [ { above_levels_ids: '', diff --git a/src/__tests__/data/cohortCreation/imagingCriteria.ts b/src/__tests__/data/cohortCreation/imagingCriteria.ts index fc2a205ff..f15ac9367 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, - encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }] + 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: ['cancelled'] } diff --git a/src/__tests__/data/cohortCreation/medicationCriteria.ts b/src/__tests__/data/cohortCreation/medicationCriteria.ts index d5c64e833..8a0f02d46 100644 --- a/src/__tests__/data/cohortCreation/medicationCriteria.ts +++ b/src/__tests__/data/cohortCreation/medicationCriteria.ts @@ -1,34 +1,23 @@ -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, - encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], + 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: ['cancelled'], code: [ { id: 'D01AA01', label: 'D01AA01 - Nystatin; Topical', system: 'https://terminology.eds.aphp.fr/atc' }, { id: 'D01AA02', label: 'D01AA02 - Natamycin; Topical', system: 'https://terminology.eds.aphp.fr/atc' }, @@ -38,13 +27,7 @@ export const completeMedicationAdministrationCriteria: MedicationDataType = { system: 'https://terminology.eds.aphp.fr/smt-medicament-ucd' } ], - administration: [ - { - id: 'CUTAN', - label: 'Cutanée', - system: 'https://terminology.eds.aphp.fr/aphp-orbis-medicament-voie-administration' - } - ], + administration: ['CUTAN'], encounterService: [ { above_levels_ids: '8312002244', @@ -66,14 +49,11 @@ 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, - encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], + 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: ['cancelled'], code: [ { id: 'D01AA01', label: 'D01AA01 - Nystatin; Topical', system: 'https://terminology.eds.aphp.fr/atc' }, { id: 'D01AA02', label: 'D01AA02 - Natamycin; Topical', system: 'https://terminology.eds.aphp.fr/atc' }, @@ -83,13 +63,7 @@ export const completeMedicationPrescriptionCriteria: MedicationDataType = { system: 'https://terminology.eds.aphp.fr/smt-medicament-ucd' } ], - prescriptionType: [ - { - id: '172641', - label: 'Prescription Hospitalière', - system: 'https://terminology.eds.aphp.fr/aphp-medicament-type-prescription' - } - ], + prescriptionType: ['172641'], encounterService: [ { above_levels_ids: '8312002244', diff --git a/src/__tests__/data/cohortCreation/observationCriteria.ts b/src/__tests__/data/cohortCreation/observationCriteria.ts index 6c1683b56..0f3175ee2 100644 --- a/src/__tests__/data/cohortCreation/observationCriteria.ts +++ b/src/__tests__/data/cohortCreation/observationCriteria.ts @@ -1,45 +1,31 @@ -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, - encounterStatus: [{ id: 'cancelled', label: 'Cancelled', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' }], + 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: ['cancelled'], 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], + enableSearchByValue: true, + 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..dd2d8d042 100644 --- a/src/__tests__/data/cohortCreation/patientCriteria.ts +++ b/src/__tests__/data/cohortCreation/patientCriteria.ts @@ -1,104 +1,53 @@ -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 = { ...defaultPatientCriteria, - genders: [ - { - id: 'f', - label: 'femme' - } - ] + genders: ['f'] } export const patientGendersCriteria: DemographicDataType = { ...defaultPatientCriteria, - genders: [ - { - id: 'f', - label: 'femme' - }, - { - id: 'm', - label: 'homme' - } - ] + genders: ['f', 'm'] } export const patientDeceasedVitalStatusCriteria: DemographicDataType = { ...defaultPatientCriteria, - vitalStatus: [ - { - id: 'true', - label: 'deceased' - } - ] + vitalStatus: ['true'] } + export const patientVitalStatusCriteria: DemographicDataType = { ...defaultPatientCriteria, - vitalStatus: [ - { - id: 'true', - label: 'deceased' - }, - { - id: 'false', - label: 'alive' - } - ] + vitalStatus: ['true', 'false'] } 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 = { ...defaultPatientCriteria, - genders: [ - { - id: 'f', - label: 'femme' - }, - { - id: 'm', - label: 'homme' - } - ], - vitalStatus: [ - { - id: 'true', - label: 'deceased' - }, - { - id: 'false', - label: 'alive' - } - ], - age: ['12/5/8', '25/7/25'], - birthdates: ['2020-01-01', '2020-12-31'], - deathDates: ['2020-01-01', '2020-12-31'] + genders: ['f', 'm'], + vitalStatus: ['true', 'false'], + //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..7caa3e945 100644 --- a/src/__tests__/data/cohortCreation/procedureCriteria.ts +++ b/src/__tests__/data/cohortCreation/procedureCriteria.ts @@ -1,38 +1,22 @@ -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, - encounterStatus: [ - { id: 'entered-in-error', label: 'Entered In Error', system: 'http://hl7.org/fhir/CodeSystem/encounter-status' } - ], + 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: ['entered-in-error'], code: [ { id: '000126', 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 e65ae7acf..20c1551cb 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 @@ -31,14 +49,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 }, { @@ -46,28 +64,14 @@ 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, @@ -75,93 +79,63 @@ const criteriaList: () => CriteriaItemType[] = () => { color: ODD_DOCUMENT_REFERENCE ? '#0063AF' : '#808080', disabled: !ODD_DOCUMENT_REFERENCE, 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() } ] }, { id: CriteriaType.MEDICATION, + types: [CriteriaType.MEDICATION_REQUEST, CriteriaType.MEDICATION_ADMINISTRATION], 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 @@ -173,12 +147,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: [ @@ -188,17 +160,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, @@ -206,39 +168,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() } ] } @@ -249,22 +179,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/Form/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/components/Form/styles.ts deleted file mode 100644 index 8765a8bcb..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm/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/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/07/2024. - - - 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/07/2024. - - - 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 ( + + {label} + {tooltip && ( + + + + )} + + ) + } + return label +} + +export const CFItem = >(props: CritieraItemProps) => { + const { valueKey, updateData, data, setError, getValueSetOptions, searchCode, viewRenderers, resetCondition } = props + const View = viewRenderers[props.type] as CriteriaFormItemView + const fieldValue = (valueKey ? data[valueKey] : undefined) as DataTypeMapping[U['type']]['dataType'] | null + const context = { deidentified: props.deidentified } + const displayCondition = props.displayCondition + + if ( + valueKey && + fieldValue !== null && + resetCondition && + ((isFunction(resetCondition) && resetCondition(data as Record, context)) || + (isString(resetCondition) && eval(resetCondition)(data, context))) + ) { + updateData({ ...data, [valueKey]: null }) + } + 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 + value: DataTypes + displayValueSummary?: string | ((value: DataTypes) => string) + context: Context + }> +) { + const { classes } = useStyles() + const { label, data, context, displayValueSummary, value } = props + const [valueSummary, setValueSummary] = useState(undefined) + + useEffect(() => { + if (isFunction(displayValueSummary)) { + setValueSummary(displayValueSummary(value)) + } else if (isString(displayValueSummary)) { + setValueSummary(eval(displayValueSummary)(value)) + } + // only value can change + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]) + const labelValue = + label && + ((isFunction(label) && label(data as Record, context)) || + (isString(label) && eval(label)(data, context))) + return ( + + {labelValue ? : ''} + {valueSummary && {valueSummary}} + {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..2bceaaa78 --- /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'occurrences" }] + } + }, + { + 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..94caf6d63 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/index.tsx @@ -0,0 +1,133 @@ +import React, { useEffect, useState } from 'react' +import CriteriaLayout from 'components/ui/CriteriaLayout' +import { + CriteriaForm as CriteriaFormDefinition, + CriteriaFormItemViewProps, + NumberAndComparatorDataType, + NewDurationRangeType, + CommonCriteriaData, + DataTypes +} from './types' +import { CFItem, CFSection } from './components' +import FORM_ITEM_RENDERER from './mappers/renderers' +import { useAppSelector } from 'state' +import { LabelObject } from 'types/searchCriterias' +import { isFunction, isString } from 'lodash' + +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, + title, + infoAlert, + warningAlert, + itemSections, + errorMessages, + globalErrorCheck, + 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 + + if (globalErrorCheck) { + const context = { deidentified } + let globalError: string | undefined = undefined + if (isFunction(globalErrorCheck)) { + globalError = globalErrorCheck(criteriaData as Record, context) + } else if (isString(globalErrorCheck)) { + globalError = eval(globalErrorCheck)(criteriaData, context) + } + if (globalError !== error) { + setError(globalError) + } + } + + useEffect(() => { + onDataChange?.(criteriaData) + }, [criteriaData, onDataChange]) + + console.log('rendering form') + console.log('test itemSections', itemSections) + return ( + setCriteriaData({ ...criteriaData, title })} + isEdition={isEdition} + goBack={goBack} + onSubmit={() => updateData(criteriaData)} + disabled={error !== undefined} + isInclusive={!!criteriaData.isInclusive} + onChangeIsInclusive={(isInclusive) => setCriteriaData({ ...criteriaData, isInclusive: isInclusive })} + infoAlert={infoAlert} + warningAlert={warningAlert} + errorAlert={error && errorMessages ? [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..298968ff2 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/buildMappers.ts @@ -0,0 +1,370 @@ +/* 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 unbuildMultiSelect = (value: string, existingValue?: string[]) => { + const values = value ? value.split(',').map((v) => v.trim()) : null + return [...(existingValue || []), ...(values || [])] +} + +const buildSelect = (criterion: string[] | string | null, hierarchyUrl?: string) => { + if (!criterion) { + return '' + } + const values = Array.isArray(criterion) ? criterion : [criterion] + return values.map((item) => (hierarchyUrl ? `${hierarchyUrl}|${item}` : item)).reduce(searchReducer, '') +} + +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) => { + const valueElements = value.split('|') + if (valueElements.length > 1) { + return { id: valueElements[1], system: valueElements[0], label: '' } + } + return { id: valueElements[0], 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 values = value.split(',') + if (values.length > 1) { + const res: NewDurationRangeType = values.reduce( + (acc, val) => unbuildDateFilter(val, acc), + existingValue + ) as NewDurationRangeType + return res + } + + 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('eq*')) { + // TODO this is really bad, but it's the only way to handle the includeNull case for now + // the value matched is derived from the param 'not (xxx eq "*")' + 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: string | null, daysOfDelay: number | null) => { + if (withDocument !== DocumentAttachmentMethod.NONE) { + return `${ + withDocument === DocumentAttachmentMethod.ACCESS_NUMBER + ? DocumentAttachmentMethod.ACCESS_NUMBER + : `INFERENCE_TEMPOREL${daysOfDelay ? `_${daysOfDelay}_J` : ''}` + }` + } + return '' +} + +const parseDocumentAttachment = (value: string) => { + const documentAttachment: { documentAttachmentMethod: DocumentAttachmentMethod; daysOfDelay: string | null } = { + documentAttachmentMethod: DocumentAttachmentMethod.NONE, + daysOfDelay: null + } + if (value === DocumentAttachmentMethod.ACCESS_NUMBER) { + documentAttachment.documentAttachmentMethod = value + } else if (value.startsWith(DocumentAttachmentMethod.INFERENCE_TEMPOREL)) { + documentAttachment.documentAttachmentMethod = DocumentAttachmentMethod.INFERENCE_TEMPOREL + const matchNumber = /\d+/.exec(value) + if (matchNumber) { + documentAttachment.daysOfDelay = matchNumber[0] + } + } + + return documentAttachment +} + +const unbuildDocumentAttachment = (value: string) => { + return parseDocumentAttachment(value).documentAttachmentMethod +} + +const unbuildDaysOfDelay = (value: string) => { + return parseDocumentAttachment(value).daysOfDelay +} + +/********************************************************************************************* */ +/* Item mapper list */ +/********************************************************************************************* */ + +export const UNBUILD_MAPPERS = { + unbuildLabelObject: async ( + val: string, + deid: boolean, + existingValue: DataTypes, + fhirkey: string, + args: Array + ) => Promise.resolve(unbuildLabelObjectFilterValue(val, existingValue as LabelObject[])), + unbuildSelect: async ( + val: string, + deid: boolean, + existingValue: DataTypes, + fhirkey: string, + args: Array + ) => Promise.resolve(unbuildMultiSelect(val, existingValue as string[])), + unbuildEncounterService: async ( + val: string, + deid: boolean, + existingValue: DataTypes, + fhirkey: string, + args: Array + ) => unbuildEncounterServiceFilter(val, existingValue as Hierarchy[]), + unbuildDate: async (val: string, deid: boolean, existingValue: DataTypes, fhirkey: string, args: Array) => + Promise.resolve(unbuildDateFilter(val, existingValue as NewDurationRangeType)), + unbuildDuration: async ( + val: string, + deid: boolean, + existingValue: DataTypes, + fhirkey: string, + args: Array + ) => Promise.resolve(unbuildDurationFilter(val, deid, existingValue as NewDurationRangeType)), + unbuildSearch: async ( + val: string, + deid: boolean, + existingValue: DataTypes, + fhirkey: string, + args: Array + ) => Promise.resolve(unbuildSearchFilter(val)), + unbuildComparator: async ( + val: string, + deid: boolean, + existingValue: DataTypes, + fhirkey: string, + args: Array + ) => Promise.resolve(parseOccurence(val)), + unbuildFromKey: async ( + val: string, + deid: boolean, + existingValue: DataTypes, + fhirkey: string, + args: Array + ) => { + return Promise.resolve(fhirkey) + }, + unbuildDocumentAttachment: async ( + val: string, + deid: boolean, + existingValue: DataTypes, + fhirkey: string, + args: Array + ) => Promise.resolve(unbuildDocumentAttachment(val)), + unbuildDaysOfDelay: async ( + val: string, + deid: boolean, + existingValue: DataTypes, + fhirkey: string, + args: Array + ) => Promise.resolve(unbuildDaysOfDelay(val)), + unbuildBooleanFromDataNonNullStatus: async ( + val: string, + deid: boolean, + existingValue: DataTypes, + fhirkey: string, + args: Array + ) => Promise.resolve(!!val) +} + +export const BUILD_MAPPERS = { + buildSelect: (val: DataTypes, key: FhirKey, deidentified: boolean, args: Array) => + buildSelect(val as string[], args[0] as string), + 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 string, args[0] as number | null), + noop: (val: DataTypes, key: FhirKey, deidentified: boolean, args: Array) => undefined +} 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..b428e77d5 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/chipDisplayMapper.tsx @@ -0,0 +1,319 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { ValueSetStore } from 'state/valueSets' +import { + AutoCompleteItem, + CodeSearchItem, + DataTypes, + GenericCriteriaItem, + NewDurationRangeType, + NumberAndComparatorDataType +} from '../types' +import React, { 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 | null) => { + const loc = searchBy === SearchByTypes.DESCRIPTION ? 'titre du document' : 'document' + return `Contient "${value}" dans le ${loc}` +} + +const getDocumentTypesLabel = (values: string[]) => { + const typeGroups = allDocTypes.docTypes.reduce((acc, docType) => { + acc[docType.type] = acc[docType.type] ? [...acc[docType.type], docType.code] : [docType.code] + return acc + }, {} as Record) + const typeMap = allDocTypes.docTypes.reduce((acc, docType) => { + acc[docType.code] = docType + return acc + }, {} as Record) + return values + .reduce((acc, selectedDocType) => { + const selectedDocTypeGroup = typeMap[selectedDocType].type + const numberOfElementFromGroup = + selectedDocTypeGroup && selectedDocTypeGroup in typeGroups ? typeGroups[selectedDocTypeGroup].length : 0 + const numberOfElementSelected = values.filter((doc) => typeMap[doc].type === selectedDocTypeGroup).length + + if (numberOfElementFromGroup === numberOfElementSelected) { + return [...acc, selectedDocTypeGroup] + } else { + return [...acc, typeMap[selectedDocType].label] + } + }, [] as string[]) + .filter((value, index, self) => self.indexOf(value) === index) + .join(' - ') +} + +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 = (documentAttachementMethod: string, daysOfDelay: string | null) => { + if (documentAttachementMethod === DocumentAttachmentMethod.INFERENCE_TEMPOREL) { + return `Rattachement aux documents par ${DocumentAttachmentMethodLabel.INFERENCE_TEMPOREL.toLocaleLowerCase()}${ + daysOfDelay !== '' && daysOfDelay !== null ? ` de ${daysOfDelay} jour(s)` : '' + }` + } else if (documentAttachementMethod === 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 && 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: string[] | string | null, + item: AutoCompleteItem, + valueSets: ValueSetStore, + label?: string, + prependCode?: boolean +) => { + if (val === null) return null + const values = Array.isArray(val) ? val : [val] + return chipFromLabelObject( + values.map((v) => ({ id: v, label: v })), + item, + getLabelsForAutoCompleteItem, + valueSets, + label, + prependCode + ) +} + +const chipFromCodeSearch = ( + val: LabelObject[] | null, + item: CodeSearchItem, + valueSets: ValueSetStore, + label?: string, + prependCode?: boolean +) => { + return chipFromLabelObject(val, item, getLabelsForCodeSearchItem, valueSets, label, prependCode) +} + +// 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, + prependCode?: boolean +) => { + if (val === null) return null + const labels = getLabel(val, item, valueSets).map( + (code) => `${displaySystem(code.system)} ${prependCode ? code.id + ' - ' : ''}${code.label}` + ) + 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 string[] | string, + item as AutoCompleteItem, + valueSets, + args[0] as string, + args[1] as boolean + ), + 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, args[1] as boolean), + 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 string, 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 string[]), + 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)(args.slice(3).find((arg, i) => i % 2 === 0) as DataTypes, item, valueSets, []) + : (args[0] as ChipDisplayMethod)(args.slice(3).find((arg, i) => i % 2 === 1) as DataTypes, item, valueSets, []) + }, + noop: () => undefined +} 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..5faef3a1b --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/index.ts @@ -0,0 +1,352 @@ +import { Comparators, 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, isFunction, 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 + } + const arg1 = parseExtraArgs(fhirKey.value1, obj, BUILD_MAPPERS) + const arg2 = parseExtraArgs(fhirKey.value2, obj, BUILD_MAPPERS) + const simplifiedArg1 = isArray(arg1) && arg1.length > 0 ? arg1[0] : arg1 + const simplifiedArg2 = isArray(arg2) && arg2.length > 0 ? arg2[0] : arg2 + return simplifiedArg1 === simplifiedArg2 ? 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, + select: BUILD_MAPPERS.buildSelect, + autocomplete: BUILD_MAPPERS.buildSelect, + 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, + fhirKey: string, + args: Array + ) => Promise +> = { + calendarRange: UNBUILD_MAPPERS.unbuildDate, + durationRange: UNBUILD_MAPPERS.unbuildDuration, + text: UNBUILD_MAPPERS.unbuildSearch, + select: UNBUILD_MAPPERS.unbuildSearch, + autocomplete: UNBUILD_MAPPERS.unbuildSelect, + 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, + select: CHIPS_DISPLAY_METHODS.autocomplete, + 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] + let value = criteria[item.valueKey] as DataTypes + if (buildInfo.ignoreIf) { + const ignore = isFunction(buildInfo.ignoreIf) + ? buildInfo.ignoreIf(criteria) + : eval(buildInfo.ignoreIf)(criteria) + if (ignore) { + value = null + } + } + 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, + criteriaDefinition: CriteriaForm +): Promise => { + const emptyCriterion: T = { ...criteriaDefinition.initialData } as T + emptyCriterion.id = element._id + emptyCriterion.type = criteriaDefinition.buildInfo.type[element.resourceType] 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: false }) + 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 matchingItems = criteriaItems.filter((item) => matchFhirKey(key, item.buildInfo?.fhirKey)) + for (const item of matchingItems) { + if (item?.buildInfo?.fhirKey && item?.valueKey) { + if ( + item.buildInfo.unbuildIgnoreValues && + item.buildInfo.unbuildIgnoreValues.find( + (ignoreValue) => JSON.stringify(ignoreValue) === JSON.stringify(value) + ) + ) { + continue + } + 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, + key, + item.buildInfo.unbuildMethodExtraArgs || [] + ) + 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) => + Object.values(criterion.formDefinition?.buildInfo?.type || {}).includes(selectedCriteria.type) + )?.formDefinition + if (!criteriaDef) return [] + + const chips = criteriaDef.itemSections + .flatMap((section) => section.items) + .map((item) => { + if (!item.buildInfo || !item.valueKey) return null + let val = selectedCriteria[item.valueKey as keyof typeof selectedCriteria] + if (item.buildInfo.ignoreIf) { + const ignore = isFunction(item.buildInfo.ignoreIf) + ? item.buildInfo.ignoreIf(selectedCriteria) + : eval(item.buildInfo.ignoreIf)(selectedCriteria) + if (ignore) { + val = null + } + } + 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) + ) + 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..6c6e7f2d2 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/mappers/renderers.tsx @@ -0,0 +1,320 @@ +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, + 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 { CriteriaLabel } from 'components/ui/CriteriaLabel' +import { Comparators } from 'types/requestCriterias' +import SimpleSelect from 'components/ui/Inputs/SimpleSelect' + +/************************************************************************************/ +/* 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 }) + } + unit={props.definition.unit} + onError={(isError) => props.setError(isError ? 'error' : undefined)} + includeDays={!props.deidentified || !!props.definition.includeDays} + /> + ) + }, + 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 + } + /> + ), + select: (props) => { + return ( + props.updateData(val)} + /> + ) + }, + autocomplete: (props) => { + const arrayPropValue = isArray(props.value) ? props.value : [props.value] + const codeSystem = props.getValueSetOptions(props.definition.valueSetId) + const groupBy = props.definition.groupBy + const valueWithLabels = (arrayPropValue ?? []).map( + (code) => codeSystem.find((c) => c.id === code) ?? { id: code, label: code } + ) + const value = props.definition.singleChoice ? valueWithLabels?.at(0) ?? null : valueWithLabels ?? [] + return ( + `${props.definition.prependCode ? option.id + ' - ' : ''}${option.label}`} + isOptionEqualToValue={(option, value) => option.id === value.id} + value={value} + onChange={(e, value) => props.updateData(value ? (isArray(value) ? value.map((v) => v.id) : [value.id]) : 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).map((doc) => doc.id)) + } else { + props.updateData(_.uniqWith([...valueWithLabels, ...groupChildren], _.isEqual).map((doc) => doc.id)) + } + } + + return ( + + + 0 + } + checked={groupChildren.length === selectedWithinGroup.length} + onClick={onClick} + indeterminateIcon={} + /> + + {group} + + + {children} + + ) + } + : undefined + } + /> + ) + }, + radioChoice: (props) => { + return ( + props.updateData(value)} + > + {props.definition.choices.map((choice) => ( + } + 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} + fullWidth + InputProps={{ + inputProps: { + min: props.definition.min + } + }} + /> + ) + }, + numberAndComparator: (props) => { + const nonNullValue = props.value ?? { value: 0, comparator: Comparators.GREATER_OR_EQUAL } + return ( + { + props.updateData({ value: newCount, comparator: newComparator, maxValue: maxValue }) + }} + withHierarchyInfo={props.definition.withHierarchyInfo} + withInfo={props.definition.info} + disabled={props.disabled} + /> + ) + }, + boolean: (props) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + return ( + + + props.updateData(event.target.checked)} + disabled={props.disabled} + /> + + + ) + }, + 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/HospitForm/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/style.ts similarity index 84% rename from src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/styles.ts rename to src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/style.ts index 84150bde6..8b74dcc13 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/styles.ts +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/style.ts @@ -2,7 +2,7 @@ import { makeStyles } from 'tss-react/mui' const useStyles = makeStyles()(() => ({ inputItem: { - margin: '1em' + margin: '1em 1em 0' } })) 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..6de207c57 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types.ts @@ -0,0 +1,366 @@ +import { ReactNode } from 'react' +import { ScopeElement } from 'types' +import { Hierarchy } from 'types/hierarchy' +import { Comparators, CriteriaType, ResourceType } 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 // the displayCondition is used to hide the field + disableCondition?: ((data: Record, context: Context) => boolean) | string // the disableCondition is used to disable the field + displayValueSummary?: (data: DataTypes) => string // the displayValueSummary is used to display a summary of the value + // for resetting the value of the field + resetCondition?: ((data: Record, context: Context) => boolean) | string // the resetCondition is used to reset the value of the field +} + +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 + displayCheckError?: boolean + extractValidValues?: boolean +} + +export type InfoCriteriaItem = BaseCriteriaItem & { + type: 'info' + content: string + contentType: 'info' | 'warning' | 'error' +} + +export type NumberCriteriaItem = BaseCriteriaItem & { + type: 'number' + min?: number +} + +export type BooleanCriteriaItem = BaseCriteriaItem & { + type: 'boolean' +} + +export type RadioChoiceCriteriaItem = BaseCriteriaItem & { + type: 'radioChoice' + choices: LabelObject[] +} + +export type NumberWithComparatorCriteriaItem = BaseCriteriaItem & { + type: 'numberAndComparator' + withHierarchyInfo?: boolean + withInfo?: ReactNode + floatValues?: boolean + allowBetween?: boolean +} + +export type DurationItem = BaseCriteriaItem & { + type: 'durationRange' + unit?: string + includeDays?: boolean +} + +export type CalendarItem = BaseCriteriaItem & + WithErrorType & { + type: 'calendarRange' + withOptionIncludeNull?: boolean + } + +export type SelectItem = BaseCriteriaItem & { + type: 'select' + choices: LabelObject[] +} + +export type AutoCompleteItem = BaseCriteriaItem & { + type: 'autocomplete' + noOptionsText: string + singleChoice?: boolean + valueSetId: string + valueSetData?: LabelObject[] + prependCode?: boolean + 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 + | SelectItem + | 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 + | 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 + select: 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: string[] +} + +/****************************************************************/ +/* 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 // the key (fhir param name) to use in the fhir filter + buildMethod?: keyof typeof BUILD_MAPPERS // one of the build mappers, if not provided the default build method for this type of criteria will be used + buildMethodExtraArgs?: Array // extra arguments to pass to the build method + ignoreIf?: ((data: Record) => boolean) | string // if true, the criteria value will be set to null and therefore ignored in the build / chip display process + unbuildMethod?: keyof typeof UNBUILD_MAPPERS // one of the unbuild mappers, if not provided the default unbuild method for this type of criteria will be used + unbuildMethodExtraArgs?: Array // extra arguments to pass to the unbuild method + unbuildIgnoreValues?: DataTypes[] // if the filter raw value is found in this list, unbuilding (setting the value to the criteria data object from the filter) will be ignored for this criteria. It is mainly used to match default values + chipDisplayMethod?: keyof typeof CHIPS_DISPLAY_METHODS // one of the chip display mappers, if not provided the default chip display method for this type of criteria will be used + chipDisplayMethodExtraArgs?: Array // extra arguments to pass to the chip display method + } +} + +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 + info?: string + defaulCollapsed?: boolean + items: CriteriaItem[] +} + +// Criteria form definition +export type CriteriaForm = { + label: string + title: string + infoAlert?: ReactNode[] + warningAlert?: ReactNode[] + initialData: Omit + errorMessages?: { [key: string]: string } + buildInfo: { + defaultFilter?: string + type: Partial> + // should return true if the criteria is to be unbuild for the current fhir filter + subType?: string + } + globalErrorCheck?: ((data: Record, context: Context) => string | undefined) | 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..f77bdc245 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, component } = 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 || component ? 'pointer' : 'default' const cursor = disabled ? 'not-allowed' : pointer @@ -164,12 +166,9 @@ const CriteriaRightPanel: React.FC = (props) => { const criteriaListWithConfig = criteriaList().map((criteriaItem) => applyConfigToCriteriaItem(criteriaItem, criteria.config) ) - const { classes } = useStyles() const [action, setAction] = useState(null) - const DrawerComponent = action ? action.components : null - const _onChangeSelectedCriteria = (newSelectedCriteria: SelectedCriteriaType) => { const _newSelectedCriteria = { ...newSelectedCriteria, @@ -185,16 +184,8 @@ const CriteriaRightPanel: React.FC = (props) => { if (!_criteria) return null for (const criteriaItem of _criteria) { - const { id, subItems } = criteriaItem - // For medication, we match request and medication administration - if ( - id === 'Medication' && - [CriteriaType.MEDICATION_REQUEST, CriteriaType.MEDICATION_ADMINISTRATION].includes(selectedCriteria.type) - ) { - return criteriaItem - } - // For others the id should match the selected criteria type - if (id === selectedCriteria.type) return criteriaItem + const { id, types, subItems } = criteriaItem + if (id === selectedCriteria.type || types?.includes(selectedCriteria.type)) return criteriaItem // Search subcriterias if (subItems) { @@ -213,21 +204,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 ba197c122..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/IPPForm/IPPForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/IPPForm/IPPForm.tsx deleted file mode 100644 index 55f43ccc0..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[2]) - } - - 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 25e0e1a91..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/index.tsx +++ /dev/null @@ -1,430 +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, 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 { 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 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/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..746e4b639 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/BiologyForm.ts @@ -0,0 +1,191 @@ +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 + enableSearchByValue: boolean + } + +export const form: () => CriteriaForm = () => ({ + label: 'de biologie', + title: '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, + enableSearchByValue: false + }, + infoAlert: ['Tous les éléments des champs multiples sont liés par une contrainte OU'], + 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." + ], + buildInfo: { + type: { [ResourceType.OBSERVATION]: CriteriaType.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'occurrences" }] + } + }, + { + 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 }, + { type: 'boolean', value: true } + ] + } + }, + { + valueKey: 'enableSearchByValue', + label: 'Activer la recherche par valeur', + info: 'Pour pouvoir rechercher par valeur, vous devez sélectionner un seul et unique analyte (élement le plus fin de la hiérarchie).', + type: 'boolean', + displayCondition: (data) => { + const typedData = data as ObservationDataType + return typedData.code?.length === 1 && !!typedData.code[0].isLeaf + }, + resetCondition: (data) => { + const typedData = data as ObservationDataType + return typedData.code?.length !== 1 || !typedData.code[0].isLeaf + }, + buildInfo: { + fhirKey: ObservationParamsKeys.VALUE, + buildMethod: 'noop', + chipDisplayMethod: 'noop', + unbuildMethod: 'unbuildBooleanFromDataNonNullStatus' + } + }, + { + valueKey: 'searchByValue', + type: 'numberAndComparator', + allowBetween: true, + withHierarchyInfo: false, + displayCondition: (data) => { + return !!data.enableSearchByValue + }, + resetCondition: (data) => { + const typedData = data as ObservationDataType + return typedData.code?.length !== 1 || !typedData.code[0].isLeaf + }, + buildInfo: { + fhirKey: ObservationParamsKeys.VALUE, + ignoreIf: (data) => { + const typedData = data as ObservationDataType + return typedData.code?.length !== 1 || !typedData.code[0].isLeaf || !data.enableSearchByValue + }, + buildMethodExtraArgs: [{ type: 'string', value: 'le0,ge0' }], + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Valeur' }], + unbuildIgnoreValues: ['le0,ge0'] + } + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + extraLabel: () => 'Statut de la visite', + 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', + extraInfo: 'Ne concerne pas les consultations.', + 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..a93005999 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/CCAMForm.ts @@ -0,0 +1,163 @@ +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: "d'actes CCAM", + title: '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' + }, + infoAlert: ['Tous les éléments des champs multiples sont liés par une contrainte OU'], + buildInfo: { + type: { [ResourceType.PROCEDURE]: CriteriaType.PROCEDURE }, + defaultFilter: 'subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurrences" }] + } + }, + { + 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/07/2024.', + 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', + extraInfo: 'Ne concerne pas les consultations.', + 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'acte CCAM", + 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..c7b8404fe --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/Cim10Form.ts @@ -0,0 +1,175 @@ +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: string[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'de diagnostic', + title: '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 + }, + infoAlert: ['Tous les éléments des champs multiples sont liés par une contrainte OU'], + buildInfo: { + type: { [ResourceType.CONDITION]: CriteriaType.CONDITION }, + defaultFilter: 'subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurrences" }] + } + }, + { + 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/07/2024.', + 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', + extraInfo: 'Ne concerne pas les consultations.', + 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..4b0105a22 --- /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 { VitalStatusOptionsLabel } from 'types/searchCriterias' +import { getConfig } from 'config' + +export type DemographicDataType = CommonCriteriaData & { + type: CriteriaType.PATIENT + genders: string[] | null + vitalStatus: string[] | null + age: NewDurationRangeType | null + birthdates: NewDurationRangeType | null + deathDates: NewDurationRangeType | null +} + +export const form: () => CriteriaForm = () => ({ + label: 'démographique', + title: '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 + }, + infoAlert: ['Tous les éléments des champs multiples sont liés par une contrainte OU'], + buildInfo: { + type: { [ResourceType.PATIENT]: CriteriaType.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', + extraLabel: () => '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) => status === '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: { + fhirKey: { main: PatientsParamsKeys.DATE_IDENTIFIED, deid: PatientsParamsKeys.DATE_DEIDENTIFIED }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Âge' }], + ignoreIf: (data) => { + const typedData = data as DemographicDataType + const birthdates = typedData.birthdates + return birthdates !== null && (birthdates.start !== null || birthdates.end !== null) + } + } + }, + { + valueKey: 'deathDates', + type: 'calendarRange', + extraLabel: () => '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) => status === 'false'))) + ) + }, + buildInfo: { + fhirKey: PatientsParamsKeys.DEATHDATE, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Décès' }], + ignoreIf: (data) => { + const typedData = data as DemographicDataType + const vitalStatus = typedData.vitalStatus + return ( + vitalStatus !== null && vitalStatus.length === 1 && !!vitalStatus.find((status) => status === 'true') + ) + } + } + } + ] + } + ] +}) 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..d74921b9c --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/DocumentsForm.ts @@ -0,0 +1,204 @@ +import { Comparators, CriteriaType, DocumentsParamsKeys, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + WithEncounterDateDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { DocumentStatuses, FilterByDocumentStatus, 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 | null + docType: string[] | null + docStatuses: string[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'de documents cliniques', + title: 'Documents cliniques', + 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: SearchByTypes.TEXT, + docType: null, + docStatuses: null + }, + infoAlert: ['Tous les éléments des champs multiples sont liés par une contrainte OU'], + buildInfo: { + type: { [ResourceType.DOCUMENTS]: CriteriaType.DOCUMENTS }, + defaultFilter: 'type:not=doc-impor&contenttype=text/plain&subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurrences" }] + } + }, + { + valueKey: 'searchBy', + type: 'select', + label: 'Rechercher dans :', + choices: [ + { id: SearchByTypes.TEXT, label: 'Corps du document' }, + { id: SearchByTypes.DESCRIPTION, label: 'Titre du document' } + ], + noOptionsText: 'Veuillez sélectionner le type de recherche désiré', + buildInfo: { + fhirKey: { + main: SearchByTypes.TEXT, + alt: SearchByTypes.DESCRIPTION, + value1: { type: 'string', value: SearchByTypes.TEXT }, + value2: { type: 'reference', value: 'searchBy' } + }, + buildMethod: 'noop', // the filter will be ignored + unbuildMethod: 'unbuildFromKey', + chipDisplayMethod: 'noop' + } + }, + { + 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', + extraInfo: 'Ne concerne pas les consultations.', + 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..127898d46 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/EncounterForm.ts @@ -0,0 +1,262 @@ +import { EncounterParamsKeys, Comparators, CriteriaType, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + NewDurationRangeType, + WithEncounterDateDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +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: string[] | null + entryMode: string[] | null + exitMode: string[] | null + priseEnChargeType: string[] | null + typeDeSejour: string[] | null + reason: string[] | null + destination: string[] | null + provenance: string[] | null + admission: string[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'de prise en charge', + title: '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 + }, + infoAlert: [ + 'Tous les éléments des champs multiples sont liés par une contrainte OU', + "Le critère de prise en charge se base sur tous les séjours et passages. Les consultations étant des prises en charge non clôturées, elles n'ont pas de date de fin. Indiquer une durée ou une date de fin de prise en charge exclue ainsi les consultations." + ], + buildInfo: { + type: { [ResourceType.ENCOUNTER]: CriteriaType.ENCOUNTER }, + defaultFilter: 'subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurrences" }] + } + }, + { + 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', + extraInfo: "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', + unit: 'Durée', + includeDays: true, + buildInfo: { + fhirKey: EncounterParamsKeys.DURATION, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Prise en charge : ' }] + } + }, + { + valueKey: 'encounterStartDate', + type: 'calendarRange', + label: 'Début de prise en charge', + extraLabel: () => 'Date 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', + info: 'Ne concerne pas les consultations.', + 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', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite', + 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..e2b6c9504 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/GHMForm.tsx @@ -0,0 +1,147 @@ +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: 'de GHM', + title: '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: [] + }, + infoAlert: ['Tous les éléments des champs multiples sont liés par une contrainte OU'], + 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: { + type: { [ResourceType.CLAIM]: CriteriaType.CLAIM }, + defaultFilter: 'patient.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurrences" }] + } + }, + { + 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', + extraInfo: 'Ne concerne pas les consultations.', + 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 classement en GHM', + 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..7b674bf68 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/HospitForm.ts @@ -0,0 +1,765 @@ +import { Comparators, CriteriaType, QuestionnaireResponseParamsKeys, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + NewDurationRangeType, + NumberAndComparatorDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { FormNames } from 'types/searchCriterias' +import { SourceType } from 'types/scope' +import { getConfig } from 'config' + +export type HospitDataType = CommonCriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterStatusDataType & { + type: CriteriaType.HOSPIT + hospitReason: string + inUteroTransfer: string[] | null + pregnancyMonitoring: string[] | null + vme: string[] | null + maturationCorticotherapie: string[] | null + chirurgicalGesture: string[] | null + childbirth: string[] | null + hospitalChildBirthPlace: string[] | null + otherHospitalChildBirthPlace: string[] | null + homeChildBirthPlace: string[] | null + childbirthMode: string[] | null + maturationReason: string[] | null + maturationModality: string[] | null + imgIndication: string[] | null + laborOrCesareanEntry: string[] | null + pathologyDuringLabor: string[] | null + obstetricalGestureDuringLabor: string[] | null + analgesieType: string[] | null + birthDeliveryDate: NewDurationRangeType | null + birthDeliveryWeeks: NumberAndComparatorDataType + birthDeliveryDays: NumberAndComparatorDataType + birthDeliveryWay: string[] | null + instrumentType: string[] | null + cSectionModality: string[] | null + presentationAtDelivery: string[] | null + birthMensurationsGrams: NumberAndComparatorDataType + birthMensurationsPercentil: NumberAndComparatorDataType + apgar1: NumberAndComparatorDataType + apgar3: NumberAndComparatorDataType + apgar5: NumberAndComparatorDataType + apgar10: NumberAndComparatorDataType + arterialPhCord: NumberAndComparatorDataType + arterialCordLactates: NumberAndComparatorDataType + birthStatus: string[] | null + postpartumHemorrhage: string[] | null + conditionPerineum: string[] | null + exitPlaceType: string[] | null + feedingType: string[] | null + complication: string[] | null + exitFeedingMode: string[] | null + exitDiagnostic: string[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: "de fiche d'hospitalisation", + title: "Fiche d'hospitalisation", + 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 + }, + infoAlert: ['Tous les éléments des champs multiples sont liés par une contrainte OU'], + buildInfo: { + type: { [ResourceType.QUESTIONNAIRE_RESPONSE]: CriteriaType.HOSPIT }, + defaultFilter: `subject.active=true&questionnaire.name=${FormNames.HOSPIT}&status=in-progress,completed`, + subType: FormNames.HOSPIT + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurrences" }] + } + }, + { + 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: QuestionnaireResponseParamsKeys.ENCOUNTER_STATUS, + 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 foetale 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', + extraLabel: () => "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..2359a7a18 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/IPPForm.ts @@ -0,0 +1,59 @@ +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: "de liste d'IPP", + title: "Liste d'IPP", + initialData: { + type: CriteriaType.IPP_LIST, + title: "Liste d'IPP", + isInclusive: true, + encounterService: null, + search: '' + }, + buildInfo: { + type: { [ResourceType.IPP_LIST]: CriteriaType.IPP_LIST } // TODO should be ResourceType.PATIENT + }, + errorMessages: { + AT_LEAST_ONE_IPP: 'Merci de renseigner au moins un IPP' + }, + globalErrorCheck: (data) => { + console.log('errocheck', data) + if (data.search && (data.search as string).length > 0) { + return undefined + } + return 'AT_LEAST_ONE_IPP' + }, + itemSections: [ + { + items: [ + { + valueKey: 'search', + type: 'textWithRegex', + regex: '(?:^|\\D+)?(8\\d{9})(?:$|\\D+)', + placeholder: "Ajouter une liste d'IPP", + displayCheckError: false, + extractValidValues: true, + displayValueSummary: (value) => { + const ippList = (value as string) + .trim() + .split(',') + .filter((ipp) => ipp.length > 0) + return `${ippList.length} IPP détecté${ippList.length > 1 ? 's' : ''}.` + }, + 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..de8d93b10 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/ImagingForm.ts @@ -0,0 +1,340 @@ +import { Comparators, CriteriaType, ImagingParamsKeys, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + NewDurationRangeType, + NumberAndComparatorDataType, + WithEncounterDateDataType, + WithEncounterStatusDataType +} from '../CriteriaForm/types' +import { DocumentAttachmentMethod, DocumentAttachmentMethodLabel } 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: string[] | null + studyDescription: string + studyProcedure: string + numberOfSeries: NumberAndComparatorDataType + numberOfIns: NumberAndComparatorDataType + withDocument: string + daysOfDelay: string | null + studyUid: string + seriesDate: NewDurationRangeType | null + seriesDescription: string + seriesProtocol: string + seriesModalities: string[] | null + seriesUid: string + } + +export const form: () => CriteriaForm = () => ({ + label: "d'imagerie", + title: "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: DocumentAttachmentMethod.NONE, + daysOfDelay: null, + studyUid: '', + seriesDate: null, + seriesDescription: '', + seriesProtocol: '', + seriesModalities: [], + seriesUid: '' + }, + infoAlert: ['Tous les éléments des champs multiples sont liés par une contrainte OU'], + warningAlert: [ + 'Seuls les examens présents dans le PACS Philips et rattachés à un Dossier Administratif (NDA) sont actuellement disponibles.', + "Le flux alimentant les métadonnées associées aux séries et aux examens est suspendu depuis le 01/02/2023 suite à la migration du PACS AP-HP. Aucun examen produit après cette date n'est disponible via Cohort360. Pour tout besoin d'examen post 01/02/2023, merci de contacter le support Cohort360 : id.recherche.support.dsn@aphp.fr" + ], + buildInfo: { + type: { [ResourceType.IMAGING]: CriteriaType.IMAGING }, + defaultFilter: 'patient.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurrences" }] + } + }, + { + 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', + info: "Une étude est un examen. Il s'agit, au sens DICOM, de l'ensemble des acquisitions réalisées durant la visite d'un patient et, s'il existe, le compte-rendu (SR) DICOM associé.", + defaulCollapsed: true, + items: [ + { + valueKey: 'studyDate', + type: 'calendarRange', + extraLabel: () => "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, + prependCode: true, + noOptionsText: 'Veuillez entrer des modalités', + buildInfo: { + fhirKey: ImagingParamsKeys.MODALITY, + chipDisplayMethodExtraArgs: [ + { type: 'string', value: "Modalités d'étude :" }, + { type: 'boolean', value: true } + ] + } + }, + { + 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', + buildInfo: { + fhirKey: ImagingParamsKeys.NB_OF_SERIES, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Nombre de séries : ' }] + } + }, + { + valueKey: 'numberOfIns', + type: 'numberAndComparator', + label: "Nombre d'instances", + buildInfo: { + fhirKey: ImagingParamsKeys.NB_OF_INS, + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'instances : " }] + } + }, + { + valueKey: 'withDocument', + type: 'select', + label: 'Méthode de rattachement à un document', + choices: [ + { + id: DocumentAttachmentMethod.NONE, + label: DocumentAttachmentMethodLabel.NONE + }, + { + id: DocumentAttachmentMethod.ACCESS_NUMBER, + label: DocumentAttachmentMethodLabel.ACCESS_NUMBER + }, + { + id: DocumentAttachmentMethod.INFERENCE_TEMPOREL, + label: DocumentAttachmentMethodLabel.INFERENCE_TEMPOREL + } + ], + buildInfo: { + fhirKey: ImagingParamsKeys.WITH_DOCUMENT, + buildMethod: 'buildWithDocument', + buildMethodExtraArgs: [{ type: 'reference', value: 'daysOfDelay' }], + chipDisplayMethod: 'getAttachmentMethod', + chipDisplayMethodExtraArgs: [{ type: 'reference', value: 'daysOfDelay' }], + unbuildMethod: 'unbuildDocumentAttachment' + } + }, + { + valueKey: 'daysOfDelay', + type: 'number', + label: 'Plage de jours', + extraLabel: () => 'Plage de jours', + errorType: 'INCOHERENT_AGE_ERROR', + displayCondition: (data) => (data.withDocument as string[])?.at(0) === 'INFERENCE_TEMPOREL', + buildInfo: { + fhirKey: ImagingParamsKeys.WITH_DOCUMENT, + buildMethod: 'noop', + chipDisplayMethod: 'noop', + unbuildMethod: 'unbuildDaysOfDelay' + } + }, + { + valueKey: 'studyUid', + type: 'textWithRegex', + extraLabel: () => "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', + info: "Une série est une des acquisitions lors d'un examen.", + defaulCollapsed: true, + items: [ + { + valueKey: 'seriesDate', + type: 'calendarRange', + extraLabel: () => '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, + prependCode: true, + noOptionsText: 'Veuillez entrer des modalités', + buildInfo: { + fhirKey: ImagingParamsKeys.SERIES_MODALITIES, + chipDisplayMethodExtraArgs: [ + { type: 'string', value: 'Modalités de la série : ' }, + { type: 'boolean', value: true } + ] + } + }, + { + valueKey: 'seriesUid', + type: 'textWithRegex', + extraLabel: () => 'Recherche par uid de série', + 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.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', + extraInfo: 'Ne concerne pas les consultations.', + 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..cf7ff2e23 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/MedicationForm.ts @@ -0,0 +1,240 @@ +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: string[] | null + type: CriteriaType.MEDICATION_REQUEST | CriteriaType.MEDICATION_ADMINISTRATION + prescriptionType: string[] | null + endOccurrence: NewDurationRangeType | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'de médicament', + title: '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 + }, + infoAlert: ['Tous les éléments des champs multiples sont liés par une contrainte OU'], + buildInfo: { + type: { + [ResourceType.MEDICATION_ADMINISTRATION]: CriteriaType.MEDICATION_ADMINISTRATION, + [ResourceType.MEDICATION_REQUEST]: CriteriaType.MEDICATION_REQUEST + }, + defaultFilter: 'subject.active=true' + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + withHierarchyInfo: true, + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurrences" }] + } + }, + { + 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, + ignoreIf: (data) => data.type === CriteriaType.MEDICATION_ADMINISTRATION + } + }, + { + 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', + extraInfo: 'Ne concerne pas les consultations.', + 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 début d'administration/prescription", + 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 fin de prescription', + 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..eb6b128ec --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/forms/PregnancyForm.ts @@ -0,0 +1,300 @@ +import { Comparators, CriteriaType, QuestionnaireResponseParamsKeys, ResourceType } from 'types/requestCriterias' +import { + CommonCriteriaData, + CriteriaForm, + NewDurationRangeType, + NumberAndComparatorDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from '../CriteriaForm/types' +import { FormNames } 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: string[] | null + foetus: NumberAndComparatorDataType + parity: NumberAndComparatorDataType + maternalRisks: string[] | null + maternalRisksPrecision: string + risksRelatedToObstetricHistory: string[] | null + risksRelatedToObstetricHistoryPrecision: string + risksOrComplicationsOfPregnancy: string[] | null + risksOrComplicationsOfPregnancyPrecision: string + corticotherapie: string[] | null + prenatalDiagnosis: string[] | null + ultrasoundMonitoring: string[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: 'de fiche de grossesse', + title: 'Fiche de grossesse', + initialData: { + type: CriteriaType.PREGNANCY, + title: 'Critère de fiche de grossesse', + isInclusive: true, + occurrence: { value: 1, comparator: Comparators.GREATER_OR_EQUAL }, + encounterService: null, + startOccurrence: null, + encounterStatus: [], + pregnancyDate: null, + pregnancyMode: null, + foetus: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + parity: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + maternalRisks: null, + maternalRisksPrecision: '', + risksRelatedToObstetricHistory: null, + risksRelatedToObstetricHistoryPrecision: '', + risksOrComplicationsOfPregnancy: null, + risksOrComplicationsOfPregnancyPrecision: '', + corticotherapie: null, + prenatalDiagnosis: null, + ultrasoundMonitoring: null + }, + infoAlert: ['Tous les éléments des champs multiples sont liés par une contrainte OU'], + buildInfo: { + type: { [ResourceType.QUESTIONNAIRE_RESPONSE]: CriteriaType.PREGNANCY }, + defaultFilter: `subject.active=true&questionnaire.name=${FormNames.PREGNANCY}&status=in-progress,completed`, + subType: FormNames.PREGNANCY + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + buildInfo: { + chipDisplayMethodExtraArgs: [{ type: 'string', value: "Nombre d'occurrences" }] + } + }, + { + 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', + extraLabel: () => 'Date de début de grossesse', + 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', + buildInfo: { + fhirKey: { + id: 'F_MATER_001017', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Nombre de fœtus' }] + } + }, + { + valueKey: 'parity', + type: 'numberAndComparator', + label: 'Parité', + buildInfo: { + fhirKey: { + id: 'F_MATER_001192', + type: 'valueInteger' + }, + chipDisplayMethodExtraArgs: [{ type: 'string', value: 'Parité' }] + } + }, + { + valueKey: 'maternalRisks', + type: 'autocomplete', + extraLabel: () => 'Risques', + 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: 'Risques liés aux antécédents maternels - 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: 'Risques liés aux antécédents obstétricaux - 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', + extraLabel: () => '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', + extraLabel: () => 'Corticothéraphie 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', + extraLabel: () => 'Risques', + 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: 'Risques ou complications de la grossesse - 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', + extraLabel: () => '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/BirthdatesRangesFilters/index.tsx b/src/components/Filters/BirthdatesRangesFilters/index.tsx index 2084985d9..41fb66059 100644 --- a/src/components/Filters/BirthdatesRangesFilters/index.tsx +++ b/src/components/Filters/BirthdatesRangesFilters/index.tsx @@ -32,7 +32,7 @@ const BirthdatesRangesFilter = ({ }, [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/Collapse/index.tsx b/src/components/ui/Collapse/index.tsx index ac33ab92f..837bebbba 100644 --- a/src/components/ui/Collapse/index.tsx +++ b/src/components/ui/Collapse/index.tsx @@ -11,12 +11,19 @@ type CollapseProps = { value?: boolean title: string children: ReactNode +<<<<<<< HEAD margin?: string info?: React.ReactNode } const Collapse = ({ value = true, title, children, margin = '0 0 5px 0', info }: PropsWithChildren) => { +======= +} + +const Collapse = ({ value = true, title, children }: PropsWithChildren) => { +>>>>>>> adf08d4d (fix: fixed some criteria label info - Ref gestion-de-projet#2354) const [checked, setChecked] = useState(value) + console.log('test children', children) return ( @@ -42,7 +49,7 @@ const Collapse = ({ value = true, title, children, margin = '0 0 5px 0', info }: - + {children}
diff --git a/src/components/ui/Collapse/styles.tsx b/src/components/ui/Collapse/styles.tsx index b5e2ce3a7..dc212dd3b 100644 --- a/src/components/ui/Collapse/styles.tsx +++ b/src/components/ui/Collapse/styles.tsx @@ -1,11 +1,7 @@ import { Collapse, styled } from '@mui/material' -type CustomProps = { - margin?: string -} - -export const CollapseWrapper = styled(Collapse)(({ margin }) => ({ - '& div': { - margin: margin +export const CollapseWrapper = styled(Collapse)(() => ({ + '& .MuiCollapse-wrapperInner > *': { + margin: '0 0 1em' } })) diff --git a/src/components/ui/CriteriaLabel/index.tsx b/src/components/ui/CriteriaLabel/index.tsx index e443fbf21..762f154e6 100644 --- a/src/components/ui/CriteriaLabel/index.tsx +++ b/src/components/ui/CriteriaLabel/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { PropsWithChildren } from 'react' import { StyledFormLabel } from './styles' import { FormLabelProps, Tooltip } from '@mui/material' import InfoIcon from '@mui/icons-material/Info' @@ -6,9 +6,10 @@ import InfoIcon from '@mui/icons-material/Info' type StyledFormLabelProps = FormLabelProps & { label: string infoIcon?: React.ReactNode + style?: React.CSSProperties } -export const CriteriaLabel = (props: StyledFormLabelProps) => { +export const CriteriaLabel = (props: PropsWithChildren) => { const { infoIcon, label } = props return ( @@ -18,6 +19,7 @@ export const CriteriaLabel = (props: StyledFormLabelProps) => { )} + {props.children} ) } diff --git a/src/components/ui/CriteriaLayout/index.tsx b/src/components/ui/CriteriaLayout/index.tsx index dedd59fc0..82f6481ec 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' @@ -10,13 +10,14 @@ type CriteriaLayoutProps = { onSubmit: () => void disabled: boolean criteriaLabel: string + mainTitle: string title: string 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> = ({ @@ -26,6 +27,7 @@ const CriteriaLayout: React.FC> = ({ disabled, criteriaLabel, children, + mainTitle, title, onChangeTitle, isInclusive, @@ -85,7 +87,7 @@ const CriteriaLayout: React.FC> = ({ )} - Critère {criteriaLabel} + {mainTitle} 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/CheckedTextfield/index.tsx b/src/components/ui/Inputs/CheckedTextfield/index.tsx new file mode 100644 index 000000000..14028786f --- /dev/null +++ b/src/components/ui/Inputs/CheckedTextfield/index.tsx @@ -0,0 +1,104 @@ +import React, { useCallback, useEffect, useState } from 'react' +import { Grid, InputAdornment, TextField, Typography } from '@mui/material' +import WarningIcon from '@mui/icons-material/Warning' +import { ErrorWrapper } from 'components/ui/Searchbar/styles' + +type CheckedTextfieldProps = { + value: string + regex: string + errorMessage?: string + placeholder?: string + multiline?: boolean + inverseCheck?: boolean + extractValidValues?: boolean + displayCheckError?: boolean + onChange: (value: string) => void + onError: (isError: boolean) => void +} + +const CheckedTextfield = ({ + value, + onChange, + onError, + errorMessage, + regex, + placeholder, + inverseCheck = false, + displayCheckError = true, + extractValidValues = false, + multiline = false +}: CheckedTextfieldProps) => { + const [error, setError] = useState(false) + const [bufferValue, setBufferValue] = useState(value) + + const extractValue = useCallback((value: string, regexp: RegExp) => { + const matches = value.matchAll(regexp) + const extractedValues = [] + for (const match of matches) { + if (match) { + extractedValues.push(match[1]) + } + } + return extractedValues + }, []) + + useEffect(() => { + const regexp = new RegExp(regex, 'gm') + + if (!bufferValue || !!regexp.exec(bufferValue) === !inverseCheck) { + setError(false) + onError(false) + if (extractValidValues && !inverseCheck) { + onChange(extractValue(bufferValue, regexp).join(',')) + } else { + onChange(bufferValue) + } + } else { + if (extractValidValues && !inverseCheck) { + onChange(extractValue(bufferValue, regexp).join(',')) + } + setError(true) + onError(true) + } + // only bufferValue can change + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bufferValue, extractValue]) + + return ( + <> + setBufferValue(event.target.value)} + minRows={4} + style={{ width: '100%' }} + error={displayCheckError && error} + InputProps={{ + ...(displayCheckError + ? { + endAdornment: ( + + {error && } + + ) + } + : {}) + }} + /> + {displayCheckError && error && ( + + + + Des caractères non autorisés ont été détectés dans votre recherche. + + {errorMessage && {errorMessage}} + + + )} + + ) +} + +export default CheckedTextfield diff --git a/src/components/ui/Inputs/DurationRange/DurationInput.tsx b/src/components/ui/Inputs/DurationRange/DurationInput.tsx index c5f8232b2..ff7543fb4 100644 --- a/src/components/ui/Inputs/DurationRange/DurationInput.tsx +++ b/src/components/ui/Inputs/DurationRange/DurationInput.tsx @@ -9,11 +9,11 @@ type DurationProps = { value: DurationType label: string disabled?: boolean - deidentified?: boolean + includeDays?: boolean onChange: (newDuration: DurationType) => void } -const DurationInput = ({ value, label, deidentified = false, disabled = false, onChange }: DurationProps) => { +const DurationInput = ({ value, label, includeDays = true, disabled = false, onChange }: DurationProps) => { const [duration, setDuration] = useState(value) useEffect(() => { @@ -25,11 +25,11 @@ const DurationInput = ({ value, label, deidentified = false, disabled = false, o return ( - + {label} - - + + - + - {!deidentified && ( + {includeDays && ( = ({ value, label, - deidentified = false, + includeDays = true, active = true, unit = 'Âge', onChange, @@ -65,7 +65,7 @@ const DurationRange: React.FC = ({ setMinDuration(newDuration)} /> @@ -73,7 +73,7 @@ const DurationRange: React.FC = ({ setMaxDuration(newDuration)} /> diff --git a/src/components/ui/Inputs/Occurences/index.tsx b/src/components/ui/Inputs/Occurences/index.tsx index dc1dc8dec..321e24e1e 100644 --- a/src/components/ui/Inputs/Occurences/index.tsx +++ b/src/components/ui/Inputs/Occurences/index.tsx @@ -1,93 +1,157 @@ -import React, { useEffect, useState } from 'react' -import { MenuItem, Select, SelectChangeEvent, TextField } from '@mui/material' +import React, { ReactNode, useEffect, useState } from 'react' +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 + withInfo?: ReactNode withHierarchyInfo?: boolean + allowBetween?: boolean + disabled?: boolean } const OccurenceInput = ({ value, comparator, + maxValue, onchange, + floatValues = false, enableNegativeValues = false, label, - withHierarchyInfo = false + withInfo, + withHierarchyInfo = false, + allowBetween = false, + disabled = false }: OccurenceInputProps) => { - const [occurrenceValue, setOccurrenceValue] = useState(value) + const [occurrenceValue, setOccurrenceValue] = useState(value.toString()) + const [upperRangeValue, setUpperRangeValue] = useState(maxValue?.toString()) const [comparatorValue, setComparatorValue] = useState(comparator) + const [error, setError] = useState() useEffect(() => { - if (!enableNegativeValues && comparatorValue === Comparators.LESS && occurrenceValue === 0) { - setOccurrenceValue(1) - onchange(1, comparatorValue) + const typedOccurenceValue = parseFloat(occurrenceValue) + const typedUpperRangeValue = upperRangeValue ? parseFloat(upperRangeValue) : undefined + if (typedUpperRangeValue !== undefined && typedOccurenceValue > typedUpperRangeValue) { + setError('INCOHERENT_VALUE_ERROR') + } else if (comparatorValue === Comparators.BETWEEN && typedUpperRangeValue === undefined) { + setError('MISSING_VALUE_ERROR') + } else { + setError(undefined) + onchange(typedOccurenceValue, comparatorValue, typedUpperRangeValue) } - }, [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 && (enableNegativeValues ? /^-?\d*\.?\d*$/ : /^\d*\.?\d*$/).exec(newValue)) || + /^\d+$/.exec(newValue) || + (enableNegativeValues && newValue === '0' && comparatorValue === Comparators.LESS) + ) { + return newValue + } + return undefined + } + + const handleValueChange = (e: React.ChangeEvent, setter: (value: string) => 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) + const typedOccurenceValue = parseFloat(occurrenceValue) + if (!enableNegativeValues && newComparator === Comparators.LESS && typedOccurenceValue === 0) { + setOccurrenceValue('1') + } + } + + const getInfoText = () => { + if (withInfo) { + return withInfo + } else if (withHierarchyInfo) { + 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. +
+ ) } + return null } + console.log('comparatorValue', comparatorValue) 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 && } + - + {comparatorValue === Comparators.BETWEEN && ( + + )} +
) } diff --git a/src/components/ui/Inputs/Occurences/styles.tsx b/src/components/ui/Inputs/Occurences/styles.tsx deleted file mode 100644 index b2101fd14..000000000 --- a/src/components/ui/Inputs/Occurences/styles.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { Grid, styled } from '@mui/material' - -export const OccurenceInputWrapper = styled(Grid)(() => ({ - display: 'grid', - gridTemplateColumns: '100px 1fr', - alignItems: 'start' -})) 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/components/ui/Inputs/SimpleSelect/index.tsx b/src/components/ui/Inputs/SimpleSelect/index.tsx new file mode 100644 index 000000000..49b9e6543 --- /dev/null +++ b/src/components/ui/Inputs/SimpleSelect/index.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import { FormControl, InputLabel, Select, MenuItem } from '@mui/material' +import { LabelObject } from 'types/searchCriterias' + +type SimpleSelectProps = { + label?: string + value: string + onChange: (value: string) => void + options: LabelObject[] +} + +const SimpleSelect = (props: SimpleSelectProps) => { + const { label, value, onChange } = props + return ( + + {label && {label}} + + + ) +} + +export default SimpleSelect diff --git a/src/components/ui/Inputs/UidTextfield/index.tsx b/src/components/ui/Inputs/UidTextfield/index.tsx deleted file mode 100644 index 75b9174bd..000000000 --- a/src/components/ui/Inputs/UidTextfield/index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { Grid, InputAdornment, TextField, Typography } from '@mui/material' -import WarningIcon from '@mui/icons-material/Warning' -import { ErrorWrapper } from 'components/ui/Searchbar/styles' - -type UidTextfieldProps = { - value: string - onChange: (value: string) => void - onError: (isError: boolean) => void -} - -const UidTextfield = ({ value, onChange, onError }: UidTextfieldProps) => { - const [error, setError] = useState(false) - - useEffect(() => { - const regex = /[^0-9.,]/ //matches everything that isn't a number, a comma or a point - - if (value.match(regex)) { - setError(true) - onError(true) - } else { - setError(false) - onError(false) - } - }, [value]) - - return ( - <> - onChange(event.target.value)} - multiline - minRows={4} - style={{ width: '100%' }} - error={error} - InputProps={{ - endAdornment: ( - - {error && } - - ) - }} - /> - {error && ( - - - - Des caractères non autorisés ont été détectés dans votre recherche. - - Seuls les chiffres, points, ou les virgules sont autorisés. - - - )} - - ) -} - -export default UidTextfield diff --git a/src/config.tsx b/src/config.tsx index ea53b8237..66bc51c15 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 { LabelObject } from 'types/searchCriterias' +import { birthStatusData, booleanFieldsData, booleanOpenChoiceFieldsData, vmeData } from 'data/questionnaire_data' type ValueSetConfig = { url: string + title?: string + data?: LabelObject[] } type AppConfig = { @@ -101,6 +105,10 @@ type AppConfig = { presentationAtDelivery: ValueSetConfig risksOrComplicationsOfPregnancy: ValueSetConfig risksRelatedToObstetricHistory: ValueSetConfig + booleanOpenChoiceFields: ValueSetConfig + booleanFields: ValueSetConfig + vme: ValueSetConfig + birthStatus: ValueSetConfig } } locationMap: { @@ -220,10 +228,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: { @@ -284,7 +292,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 7b9e9b6e8..67d10b64b 100644 --- a/src/state/cohortCreation.ts +++ b/src/state/cohortCreation.ts @@ -484,7 +484,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 51de0a8f3..cab3cc7f8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,7 +25,6 @@ import { AxiosResponse } from 'axios' import { SearchInputError } from 'types/error' import { Comparators, - CriteriaDataKey, CriteriaType, MedicationLabel, PMSIResourceTypes, @@ -36,6 +35,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', @@ -359,34 +361,30 @@ 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 + types?: 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 @@ -745,7 +743,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 9a732f9a8..fe86c54ed 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', @@ -185,206 +194,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 = '<', @@ -394,154 +233,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 4973aa40c..fa0f8e575 100644 --- a/src/types/searchCriterias.ts +++ b/src/types/searchCriterias.ts @@ -223,11 +223,14 @@ export enum DocumentAttachmentMethodLabel { INFERENCE_TEMPOREL = 'Inférence temporelle' } +// 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..c73214be8 100644 --- a/src/utils/cohortCreation.ts +++ b/src/utils/cohortCreation.ts @@ -1,106 +1,28 @@ /* 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 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 +31,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 +73,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 +99,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 +111,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 +126,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 +174,19 @@ 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) => + Object.values(crit.formDefinition?.buildInfo?.type || {}).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 +212,52 @@ const mapCriteriaToResource = (criteriaType: CriteriaType): ResourceType => { throw new Error('Unknown criteria type') } +const findQuestionnaireName = (filters: string[]) => { + const regex = /questionnaire.name=(.*)/ + for (const item of filters) { + const match = regex.exec(item) + if (match?.[1]) { + return match[1] + } + } +} + +export const unbuildCriteriaData = async ( + element: RequeteurCriteriaType, + critieriaDefinitions: CriteriaItemType[] +): Promise => { + const criteriaDefinition = critieriaDefinitions.filter((item) => + Object.keys(item.formDefinition?.buildInfo.type || {}).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].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.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 +275,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 +319,9 @@ export function buildRequest( } } } - children = [...children, child] + if (child) { + children = [...children, child] + } } return children } @@ -869,1027 +353,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 +407,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 +521,55 @@ 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 => { + console.log('fetching criteria codes') + const updatedCriteriaData: CodeCache = { ...oldCriteriaCache } + const allCriterias = getAllCriteriaItems(criteriaList) + for (const criteria of allCriterias) { + const criteriaValues = selectedCriteria.filter( + (criterion) => criterion.type === criteria.id || criteria.types?.includes(criterion.type) + ) + 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 keyof SelectedCriteriaType + const labelValues = criterion[dataKey] as unknown as LabelObject[] + if (labelValues && labelValues.length > 0) { + for (const code of labelValues) { + console.log('fetching code', code) + const codeSystem = code.system ?? defaultValueSet + const valueSetCodeCache = updatedCriteriaData[codeSystem] ?? [] + if (!valueSetCodeCache.find((data) => data.id === code.id)) { + try { + const fetchedCode = (await fetchValueSet(codeSystem, { + search: code.id || '', + noStar: true + })) as LabelObject[] + valueSetCodeCache.push(...fetchedCode) + } catch (e) { + // fail silently + console.error(`Error fetching code ${code.id} from system ${codeSystem}`, e) } } + 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/documentAttachment.ts b/src/utils/documentAttachment.ts deleted file mode 100644 index 6a3dd6b27..000000000 --- a/src/utils/documentAttachment.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DocumentAttachmentMethod } from 'types/searchCriterias' - -export const parseDocumentAttachment = (value: DocumentAttachmentMethod) => { - const documentAttachment: { documentAttachmentMethod: DocumentAttachmentMethod; daysOfDelay: string | null } = { - documentAttachmentMethod: DocumentAttachmentMethod.NONE, - daysOfDelay: null - } - if (value === DocumentAttachmentMethod.ACCESS_NUMBER) { - documentAttachment.documentAttachmentMethod = value - } else if (value.startsWith(DocumentAttachmentMethod.INFERENCE_TEMPOREL)) { - documentAttachment.documentAttachmentMethod = DocumentAttachmentMethod.INFERENCE_TEMPOREL - const matchNumber = value.match(/\d+/) - if (matchNumber) { - documentAttachment.daysOfDelay = matchNumber[0] - } - } - - return documentAttachment -} 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 -} diff --git a/src/utils/valueComparator.ts b/src/utils/valueComparator.ts index c59b3e6c1..893cb8499 100644 --- a/src/utils/valueComparator.ts +++ b/src/utils/valueComparator.ts @@ -42,7 +42,7 @@ export const filterToComparator = (filter: string) => { } export const parseOccurence = (value: string) => { - const match = value.match(/^(lt|le|gt|ge)?(-?\d*\.?\d*)$/) + const match = value.match(/^(eq|lt|le|gt|ge)?(-?\d*\.?\d*)$/) if (match) { const [, comparator, number] = match const criterion = {