diff --git a/.prettierignore b/.prettierignore index 838d7564f4..73880d10c1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,3 @@ mockServiceWorker.js dev-fss-k9saksbehandling.yml -prod-fss-k9saksbehandling.yml \ No newline at end of file +prod-fss-k9saksbehandling.yml diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 0000000000..2ad20302ff --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,17 @@ +/* eslint-disable import/extensions */ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable global-require */ +import { defineConfig } from 'cypress'; + +export default defineConfig({ + video: false, + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require('./cypress/plugins/index.js')(on, config); + }, + baseUrl: 'http://localhost:8484', + testIsolation: false, + }, +}); diff --git a/cypress/.eslintrc.json b/cypress/.eslintrc.json new file mode 100644 index 0000000000..7eba2f7399 --- /dev/null +++ b/cypress/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "plugin:cypress/recommended" + ] +} \ No newline at end of file diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 0000000000..9c2217645c --- /dev/null +++ b/cypress/plugins/index.js @@ -0,0 +1,20 @@ +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +/** + * @type {Cypress.PluginConfig} + */ +// eslint-disable-next-line no-unused-vars + +module.exports = (on, config) => {}; diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000000..112cf63f17 --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,26 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +import '@testing-library/cypress/add-commands'; diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js new file mode 100644 index 0000000000..0279775518 --- /dev/null +++ b/cypress/support/e2e.js @@ -0,0 +1,19 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/eslint/eslintrc.common.js b/eslint/eslintrc.common.js index 9dd2ad6a0e..4aa9282929 100644 --- a/eslint/eslintrc.common.js +++ b/eslint/eslintrc.common.js @@ -39,7 +39,7 @@ const config = { 'import/extensions': ['error', 'ignorePackages', { js: 'never', jsx: 'never', ts: 'never', tsx: 'never' }], 'linebreak-style': OFF, 'import/no-named-as-default': OFF, - 'max-len': [ERROR, 160], + 'max-len': [1, 160], 'no-undef': ERROR, 'react/require-default-props': OFF, 'react/jsx-filename-extension': OFF, @@ -61,6 +61,7 @@ const config = { unnamedComponents: 'arrow-function', }, ], + 'import/prefer-default-export': OFF, // note you must disable the base rule as it can report incorrect errors 'no-nested-ternary': OFF, diff --git a/jest.config.js b/jest.config.js index 976cb5b104..f49b15dfd2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,7 +9,7 @@ module.exports = { moduleNameMapper: { '\\.(svg)$': '/_mocks/fileMock.js', '\\.(less|css)$': 'identity-obj-proxy', - uuid: require.resolve('uuid'), + '^uuid': require.resolve('uuid'), }, roots: ['/packages/'], setupFiles: ['/setup/setup.js'], diff --git a/package.json b/package.json index da2bfd545e..5c3c602264 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,11 @@ "@formatjs/intl-numberformat": "8.8.0", "@hookform/error-message": "^2.0.1", "@navikt/aksel-icons": "5.10.0", - "@navikt/ds-css": "5.10.0", - "@navikt/ds-react": "5.10.0", + "@navikt/ds-css": "5.11.2", + "@navikt/ds-react": "5.11.2", "@navikt/ds-tailwind": "^5.10.0", "@navikt/familie-endringslogg": "10.0.0", - "@navikt/ft-plattform-komponenter": "2.3.10", + "@navikt/ft-plattform-komponenter": "2.3.14", "@popperjs/core": "2.11.8", "@sentry/browser": "7.80.1", "@storybook/testing-react": "2.0.1", @@ -95,6 +95,7 @@ "@storybook/storybook-deployer": "2.8.16", "@storybook/theming": "7.5.3", "@svgr/webpack": "8.1.0", + "@testing-library/cypress": "^10.0.1", "@testing-library/dom": "8.20.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "12.1.5", @@ -124,6 +125,7 @@ "cross-env": "7.0.3", "css-loader": "6.8.1", "css-minimizer-webpack-plugin": "5.0.1", + "cypress": "^13.5.1", "enzyme": "3.11.0", "eslint": "8.54.0", "eslint-config-airbnb": "19.0.4", @@ -131,6 +133,7 @@ "eslint-import-resolver-lerna": "2.0.0", "eslint-import-resolver-webpack": "0.13.8", "eslint-loader": "4.0.2", + "eslint-plugin-cypress": "^2.15.1", "eslint-plugin-import": "2.29.0", "eslint-plugin-jest": "27.6.0", "eslint-plugin-jest-dom": "4.0.3", @@ -144,6 +147,7 @@ "identity-obj-proxy": "3.0.0", "ignore-styles": "5.0.1", "jest": "29.7.0", + "jest-axe": "^8.0.0", "jest-environment-jsdom": "29.7.0", "jest-runner-eslint": "2.1.2", "jest-transform-stub": "2.0.0", diff --git a/packages/behandling-anke/package.json b/packages/behandling-anke/package.json index d56548003a..aa10c50697 100644 --- a/packages/behandling-anke/package.json +++ b/packages/behandling-anke/package.json @@ -26,7 +26,7 @@ "nav-frontend-typografi": "4.0.2", "nav-frontend-typografi-style": "2.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/behandling-felles/package.json b/packages/behandling-felles/package.json index 7bc3b5aec5..7448c614cf 100644 --- a/packages/behandling-felles/package.json +++ b/packages/behandling-felles/package.json @@ -15,11 +15,7 @@ "@k9-sak-web/modal-sett-pa-vent": "1.0.0", "@k9-sak-web/rest-api-hooks": "1.0.0", "@k9-sak-web/types": "1.0.0", - "@navikt/ft-plattform-komponenter": "2.3.10", - "@navikt/k9-fe-array-utils": "1.0.6", - "@navikt/k9-fe-bem-utils": "1.0.4", - "@navikt/k9-fe-date-utils": "1.0.9", - "@navikt/k9-fe-period-utils": "1.0.12", + "@navikt/ft-plattform-komponenter": "2.3.14", "classnames": "2.3.2", "nav-frontend-core": "6.0.1", "nav-frontend-grid": "2.0.2", @@ -35,7 +31,7 @@ "nav-frontend-typografi": "4.0.2", "nav-frontend-typografi-style": "2.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux-form": "8.3.10", "reselect": "4.1.8" diff --git a/packages/behandling-frisinn/package.json b/packages/behandling-frisinn/package.json index e6f5138ff9..479939ff02 100644 --- a/packages/behandling-frisinn/package.json +++ b/packages/behandling-frisinn/package.json @@ -23,11 +23,11 @@ "@k9-sak-web/rest-api": "1.0.0", "@k9-sak-web/rest-api-hooks": "1.0.0", "@k9-sak-web/types": "1.0.0", - "@navikt/ft-fakta-beregning": "5.0.13", - "@navikt/ft-fakta-beregning-redesign": "1.0.12", - "@navikt/ft-prosess-beregningsgrunnlag": "6.3.16", + "@navikt/ft-fakta-beregning": "5.0.17", + "@navikt/ft-fakta-beregning-redesign": "1.0.16", + "@navikt/ft-prosess-beregningsgrunnlag": "6.3.20", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/behandling-innsyn/package.json b/packages/behandling-innsyn/package.json index 927eb4901e..905ebc2d20 100644 --- a/packages/behandling-innsyn/package.json +++ b/packages/behandling-innsyn/package.json @@ -19,7 +19,7 @@ "nav-frontend-typografi": "4.0.2", "nav-frontend-typografi-style": "2.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/behandling-klage/package.json b/packages/behandling-klage/package.json index 2ed4dc9fb5..b9e00653ab 100644 --- a/packages/behandling-klage/package.json +++ b/packages/behandling-klage/package.json @@ -26,7 +26,7 @@ "nav-frontend-typografi": "4.0.2", "nav-frontend-typografi-style": "2.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/behandling-omsorgspenger/package.json b/packages/behandling-omsorgspenger/package.json index 36aae09fea..afc63d1d64 100644 --- a/packages/behandling-omsorgspenger/package.json +++ b/packages/behandling-omsorgspenger/package.json @@ -24,22 +24,21 @@ "@k9-sak-web/behandling-felles": "1.0.0", "@k9-sak-web/fakta-barn-og-overfoeringsdager": "1.0.0", "@k9-sak-web/fakta-barn-oms": "1.0.0", + "@k9-sak-web/fakta-inntektsmelding": "1.0.0", + "@k9-sak-web/fakta-omsorgen-for": "1.0.0", "@k9-sak-web/konstanter": "1.0.0", "@k9-sak-web/prosess-aarskvantum-oms": "1.0.0", "@k9-sak-web/prosess-vilkar-soknadsfrist": "1.0.0", "@k9-sak-web/rest-api": "1.0.0", "@k9-sak-web/rest-api-hooks": "1.0.0", "@k9-sak-web/types": "1.0.0", - "@navikt/ft-fakta-beregning": "5.0.13", - "@navikt/ft-fakta-beregning-redesign": "1.0.12", - "@navikt/ft-fakta-fordel-beregningsgrunnlag": "7.3.16", - "@navikt/ft-prosess-beregningsgrunnlag": "6.3.16", - "@navikt/k9-fe-http-utils": "1.0.8", - "@navikt/k9-fe-inntektsmelding": "1.0.2", - "@navikt/k9-fe-omsorgen-for": "1.0.2", + "@navikt/ft-fakta-beregning": "5.0.17", + "@navikt/ft-fakta-beregning-redesign": "1.0.16", + "@navikt/ft-fakta-fordel-beregningsgrunnlag": "7.3.20", + "@navikt/ft-prosess-beregningsgrunnlag": "6.3.20", "nav-frontend-popover": "2.0.4", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/behandling-opplaeringspenger/package.json b/packages/behandling-opplaeringspenger/package.json index afb412ce61..4b7359f15e 100644 --- a/packages/behandling-opplaeringspenger/package.json +++ b/packages/behandling-opplaeringspenger/package.json @@ -25,7 +25,12 @@ "@fpsak-frontend/shared-components": "1.0.0", "@fpsak-frontend/utils": "1.0.0", "@k9-sak-web/behandling-felles": "1.0.0", + "@k9-sak-web/fakta-etablert-tilsyn": "1.0.0", + "@k9-sak-web/fakta-inntektsmelding": "1.0.0", "@k9-sak-web/fakta-institusjon": "1.0.0", + "@k9-sak-web/fakta-medisinsk-vilkar": "1.0.0", + "@k9-sak-web/fakta-om-barnet": "1.0.0", + "@k9-sak-web/fakta-omsorgen-for": "1.0.0", "@k9-sak-web/konstanter": "1.0.0", "@k9-sak-web/prosess-uttak": "1.0.0", "@k9-sak-web/prosess-vilkar-soknadsfrist": "1.0.0", @@ -34,20 +39,20 @@ "@k9-sak-web/rest-api-hooks": "1.0.0", "@k9-sak-web/sak-app": "1.0.0", "@k9-sak-web/types": "1.0.0", - "@navikt/ft-fakta-beregning": "5.0.13", - "@navikt/ft-fakta-beregning-redesign": "1.0.12", - "@navikt/ft-fakta-fordel-beregningsgrunnlag": "7.3.16", - "@navikt/ft-prosess-beregningsgrunnlag": "6.3.16", - "@navikt/k9-fe-etablert-tilsyn": "1.0.2", + "@navikt/ft-fakta-beregning": "5.0.17", + "@navikt/ft-fakta-beregning-redesign": "1.0.16", + "@navikt/ft-fakta-fordel-beregningsgrunnlag": "7.3.20", + "@navikt/ft-prosess-beregningsgrunnlag": "6.3.20", + "@navikt/k9-fe-etablert-tilsyn": "1.0.3", "@navikt/k9-fe-http-utils": "1.0.8", - "@navikt/k9-fe-inntektsmelding": "1.0.2", - "@navikt/k9-fe-medisinsk-vilkar": "1.0.2", - "@navikt/k9-fe-om-barnet": "1.0.2", - "@navikt/k9-fe-omsorgen-for": "1.0.2", + "@navikt/k9-fe-inntektsmelding": "1.0.3", + "@navikt/k9-fe-medisinsk-vilkar": "1.0.3", + "@navikt/k9-fe-om-barnet": "1.0.3", + "@navikt/k9-fe-omsorgen-for": "1.0.3", "axios": "1.6.2", "nav-frontend-alertstriper": "4.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/behandling-pleiepenger-sluttfase/package.json b/packages/behandling-pleiepenger-sluttfase/package.json index 6337418a49..4e0b23daf4 100644 --- a/packages/behandling-pleiepenger-sluttfase/package.json +++ b/packages/behandling-pleiepenger-sluttfase/package.json @@ -24,6 +24,8 @@ "@fpsak-frontend/shared-components": "1.0.0", "@fpsak-frontend/utils": "1.0.0", "@k9-sak-web/behandling-felles": "1.0.0", + "@k9-sak-web/fakta-inntektsmelding": "1.0.0", + "@k9-sak-web/fakta-medisinsk-vilkar": "1.0.0", "@k9-sak-web/fakta-utenlandsopphold": "1.0.0", "@k9-sak-web/konstanter": "1.0.0", "@k9-sak-web/prosess-uttak": "1.0.0", @@ -33,17 +35,17 @@ "@k9-sak-web/rest-api-hooks": "1.0.0", "@k9-sak-web/sak-app": "1.0.0", "@k9-sak-web/types": "1.0.0", - "@navikt/ft-fakta-beregning": "5.0.13", - "@navikt/ft-fakta-beregning-redesign": "1.0.12", - "@navikt/ft-fakta-fordel-beregningsgrunnlag": "7.3.16", - "@navikt/ft-prosess-beregningsgrunnlag": "6.3.16", + "@navikt/ft-fakta-beregning": "5.0.17", + "@navikt/ft-fakta-beregning-redesign": "1.0.16", + "@navikt/ft-fakta-fordel-beregningsgrunnlag": "7.3.20", + "@navikt/ft-prosess-beregningsgrunnlag": "6.3.20", "@navikt/k9-fe-http-utils": "1.0.8", - "@navikt/k9-fe-inntektsmelding": "1.0.2", - "@navikt/k9-fe-medisinsk-vilkar": "1.0.2", + "@navikt/k9-fe-inntektsmelding": "1.0.3", + "@navikt/k9-fe-medisinsk-vilkar": "1.0.3", "axios": "1.6.2", "nav-frontend-alertstriper": "4.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/behandling-pleiepenger/package.json b/packages/behandling-pleiepenger/package.json index 46e5cb7b6c..e07e16f320 100644 --- a/packages/behandling-pleiepenger/package.json +++ b/packages/behandling-pleiepenger/package.json @@ -25,6 +25,11 @@ "@fpsak-frontend/shared-components": "1.0.0", "@fpsak-frontend/utils": "1.0.0", "@k9-sak-web/behandling-felles": "1.0.0", + "@k9-sak-web/fakta-etablert-tilsyn": "1.0.0", + "@k9-sak-web/fakta-inntektsmelding": "1.0.0", + "@k9-sak-web/fakta-medisinsk-vilkar": "1.0.0", + "@k9-sak-web/fakta-om-barnet": "1.0.0", + "@k9-sak-web/fakta-omsorgen-for": "1.0.0", "@k9-sak-web/fakta-utenlandsopphold": "1.0.0", "@k9-sak-web/konstanter": "1.0.0", "@k9-sak-web/prosess-uttak": "1.0.0", @@ -35,20 +40,20 @@ "@k9-sak-web/sak-app": "1.0.0", "@k9-sak-web/types": "1.0.0", "@navikt/diagnosekoder": "^1.2023.0", - "@navikt/ft-fakta-beregning": "5.0.13", - "@navikt/ft-fakta-beregning-redesign": "1.0.12", - "@navikt/ft-fakta-fordel-beregningsgrunnlag": "7.3.16", - "@navikt/ft-prosess-beregningsgrunnlag": "6.3.16", - "@navikt/k9-fe-etablert-tilsyn": "1.0.2", + "@navikt/ft-fakta-beregning": "5.0.17", + "@navikt/ft-fakta-beregning-redesign": "1.0.16", + "@navikt/ft-fakta-fordel-beregningsgrunnlag": "7.3.20", + "@navikt/ft-prosess-beregningsgrunnlag": "6.3.20", + "@navikt/k9-fe-etablert-tilsyn": "1.0.3", "@navikt/k9-fe-http-utils": "1.0.8", - "@navikt/k9-fe-inntektsmelding": "1.0.2", - "@navikt/k9-fe-medisinsk-vilkar": "1.0.2", - "@navikt/k9-fe-om-barnet": "1.0.2", - "@navikt/k9-fe-omsorgen-for": "1.0.2", + "@navikt/k9-fe-inntektsmelding": "1.0.3", + "@navikt/k9-fe-medisinsk-vilkar": "1.0.3", + "@navikt/k9-fe-om-barnet": "1.0.3", + "@navikt/k9-fe-omsorgen-for": "1.0.3", "axios": "1.6.2", "nav-frontend-alertstriper": "4.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/behandling-tilbakekreving/package.json b/packages/behandling-tilbakekreving/package.json index 28d6ccdb11..26e7b17209 100644 --- a/packages/behandling-tilbakekreving/package.json +++ b/packages/behandling-tilbakekreving/package.json @@ -21,12 +21,12 @@ "@k9-sak-web/rest-api": "1.0.0", "@k9-sak-web/rest-api-hooks": "1.0.0", "@k9-sak-web/types": "1.0.0", - "@navikt/ft-kodeverk": "^2.2.6", - "@navikt/ft-prosess-tilbakekreving-foreldelse": "^3.1.13", + "@navikt/ft-kodeverk": "^2.2.10", + "@navikt/ft-prosess-tilbakekreving-foreldelse": "3.1.17", "nav-frontend-typografi": "4.0.2", "nav-frontend-typografi-style": "2.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/behandling-unntak/package.json b/packages/behandling-unntak/package.json index 8ec877b2fa..c4b7330a4a 100644 --- a/packages/behandling-unntak/package.json +++ b/packages/behandling-unntak/package.json @@ -38,7 +38,7 @@ "nav-frontend-typografi": "4.0.2", "nav-frontend-typografi-style": "2.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/behandling-utvidet-rett/package.json b/packages/behandling-utvidet-rett/package.json index bbbfe11e0a..8443ae48d2 100644 --- a/packages/behandling-utvidet-rett/package.json +++ b/packages/behandling-utvidet-rett/package.json @@ -13,14 +13,14 @@ "@fpsak-frontend/utils": "1.0.0", "@k9-sak-web/behandling-felles": "1.0.0", "@k9-sak-web/fakta-barn-oms": "1.0.0", + "@k9-sak-web/fakta-omsorgsdager": "1.0.0", "@k9-sak-web/konstanter": "1.0.0", "@k9-sak-web/prosess-aarskvantum-oms": "1.0.0", "@k9-sak-web/rest-api": "1.0.0", "@k9-sak-web/rest-api-hooks": "1.0.0", "@k9-sak-web/types": "1.0.0", - "@navikt/k9-fe-omsorgsdager": "1.0.1", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "react-uuid": "2.0.0", "redux": "4.2.1", diff --git a/packages/fakta-arbeidsforhold/package.json b/packages/fakta-arbeidsforhold/package.json index 155c04fb4d..024588f008 100644 --- a/packages/fakta-arbeidsforhold/package.json +++ b/packages/fakta-arbeidsforhold/package.json @@ -21,7 +21,7 @@ "nav-frontend-typografi-style": "2.0.2", "prop-types": "15.8.1", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/fakta-barn-og-overfoeringsdager/package.json b/packages/fakta-barn-og-overfoeringsdager/package.json index c0d3a7e5ff..0ad0711a3d 100644 --- a/packages/fakta-barn-og-overfoeringsdager/package.json +++ b/packages/fakta-barn-og-overfoeringsdager/package.json @@ -29,7 +29,7 @@ "nav-frontend-typografi": "4.0.2", "nav-frontend-typografi-style": "2.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux-form": "8.3.10", "uuid": "9.0.1" diff --git a/packages/fakta-barn-oms/package.json b/packages/fakta-barn-oms/package.json index 59066f6803..c8f787ef6f 100644 --- a/packages/fakta-barn-oms/package.json +++ b/packages/fakta-barn-oms/package.json @@ -22,7 +22,7 @@ "nav-frontend-typografi": "4.0.2", "nav-frontend-typografi-style": "2.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "uuid": "9.0.1" }, "devDependencies": { diff --git a/packages/fakta-bosted-soker/package.json b/packages/fakta-bosted-soker/package.json index 177e37b5c4..363e9aa37d 100644 --- a/packages/fakta-bosted-soker/package.json +++ b/packages/fakta-bosted-soker/package.json @@ -17,7 +17,7 @@ "nav-frontend-typografi-style": "2.0.2", "prop-types": "15.8.1", "react": "18.2.0", - "react-intl": "6.5.2" + "react-intl": "6.5.5" }, "devDependencies": { "@fpsak-frontend/utils-test": "1.0.0", diff --git a/packages/fakta-direkte-overgang/package.json b/packages/fakta-direkte-overgang/package.json index 9f9f4df6af..bb4112f34f 100644 --- a/packages/fakta-direkte-overgang/package.json +++ b/packages/fakta-direkte-overgang/package.json @@ -21,7 +21,7 @@ "nav-frontend-typografi": "4.0.2", "nav-frontend-typografi-style": "2.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "yup": "0.32.11" }, "devDependencies": { diff --git a/packages/fakta-etablert-tilsyn/cypress/.eslintrc.json b/packages/fakta-etablert-tilsyn/cypress/.eslintrc.json new file mode 100644 index 0000000000..7eba2f7399 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/cypress/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "plugin:cypress/recommended" + ] +} \ No newline at end of file diff --git a/packages/fakta-etablert-tilsyn/cypress/e2e/EtablertTilsyn.cy.js b/packages/fakta-etablert-tilsyn/cypress/e2e/EtablertTilsyn.cy.js new file mode 100644 index 0000000000..38cb5ed21f --- /dev/null +++ b/packages/fakta-etablert-tilsyn/cypress/e2e/EtablertTilsyn.cy.js @@ -0,0 +1,25 @@ +describe('Sykdom', () => { + before(() => { + cy.visit('/'); + }); + it('skal ha skjema for håndtering av beredskap', () => { + cy.contains('Beredskap').click(); + cy.contains('Vurdering av beredskap').should('exist'); + cy.get('[name="begrunnelse"]').should('exist'); + cy.get('input[id="perioder[0].fom"]').should('not.exist'); + cy.get('input[id="perioder[0].tom"]').should('not.exist'); + cy.get('input[id="jaDeler"]').click(); + cy.get('input[id="perioder[0].fom"]').should('exist'); + cy.get('input[id="perioder[0].tom"]').should('exist'); + }); + it('skal ha skjema for håndtering av nattevåk', () => { + cy.contains('Nattevåk').click(); + cy.contains('Vurdering av nattevåk').should('exist'); + cy.get('[name="begrunnelse"]').should('exist'); + cy.get('input[id="perioder[0].fom"]').should('not.exist'); + cy.get('input[id="perioder[0].tom"]').should('not.exist'); + cy.get('input[id="jaDeler"]').click(); + cy.get('input[id="perioder[0].fom"]').should('exist'); + cy.get('input[id="perioder[0].tom"]').should('exist'); + }); +}); diff --git a/packages/fakta-etablert-tilsyn/index.html b/packages/fakta-etablert-tilsyn/index.html new file mode 100644 index 0000000000..0a2415aa1e --- /dev/null +++ b/packages/fakta-etablert-tilsyn/index.html @@ -0,0 +1,30 @@ + + + Etablert tilsyn + + + +
+ + + diff --git a/packages/fakta-etablert-tilsyn/index.ts b/packages/fakta-etablert-tilsyn/index.ts new file mode 100644 index 0000000000..05b9106588 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as EtablertTilsyn } from './src/ui/MainComponent'; diff --git a/packages/fakta-etablert-tilsyn/mock/api-mock.ts b/packages/fakta-etablert-tilsyn/mock/api-mock.ts new file mode 100644 index 0000000000..40d97d5933 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/mock/api-mock.ts @@ -0,0 +1,13 @@ +/* eslint-disable import/prefer-default-export */ +import { rest } from 'msw'; +import mockedSykdom from './mocked-data/mockedSykdom'; +import mockedTilsyn from './mocked-data/mockedTilsyn'; + +export const handlers = [ + rest.get('http://localhost:8082/mock/tilsyn', (req, res, ctx) => res(ctx.status(200), ctx.json(mockedTilsyn))), + rest.get('http://localhost:8082/mock/sykdom', (req, res, ctx) => res(ctx.status(200), ctx.json(mockedSykdom))), + + rest.get('http://localhost:8082/mock/sykdomInnleggelse', (req, res, ctx) => + res(ctx.status(200), ctx.json({ perioder: [] })), + ), +]; diff --git a/packages/fakta-etablert-tilsyn/mock/mocked-data/mockedSykdom.ts b/packages/fakta-etablert-tilsyn/mock/mocked-data/mockedSykdom.ts new file mode 100644 index 0000000000..d6e3f0eb08 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/mock/mocked-data/mockedSykdom.ts @@ -0,0 +1,45 @@ +export default { + resterendeVurderingsperioder: [], + vurderingselementer: [ + { + id: '999997', + resultat: 'IKKE_OPPFYLT', + periode: { + fom: '2022-09-20', + tom: '2022-09-20', + }, + }, + { + id: '999997', + resultat: 'IKKE_OPPFYLT', + periode: { + fom: '2022-09-22', + tom: '2022-09-22', + }, + }, + { + id: '999998', + resultat: 'IKKE_OPPFYLT', + periode: { + fom: '2022-10-05', + tom: '2022-10-05', + }, + }, + { + id: '999999', + resultat: 'IKKE_OPPFYLT', + periode: { + fom: '2022-10-05', + tom: '2022-10-05', + }, + }, + { + id: '9999999', + resultat: 'IKKE_OPPFYLT', + periode: { + fom: '2022-10-24', + tom: '2022-10-25', + }, + }, + ], +}; diff --git a/packages/fakta-etablert-tilsyn/mock/mocked-data/mockedTilsyn.ts b/packages/fakta-etablert-tilsyn/mock/mocked-data/mockedTilsyn.ts new file mode 100644 index 0000000000..83b57375a1 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/mock/mocked-data/mockedTilsyn.ts @@ -0,0 +1,361 @@ +export default { + etablertTilsynPerioder: [ + { periode: { fom: '2022-08-17', tom: '2022-08-17' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-08-18', tom: '2022-08-19' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-08-22', tom: '2022-08-23' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-08-24', tom: '2022-08-24' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-08-25', tom: '2022-08-26' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-08-29', tom: '2022-08-30' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-08-31', tom: '2022-08-31' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-01', tom: '2022-09-02' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-05', tom: '2022-09-06' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-07', tom: '2022-09-07' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-08', tom: '2022-09-09' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-12', tom: '2022-09-13' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-14', tom: '2022-09-14' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-15', tom: '2022-09-16' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-19', tom: '2022-09-20' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-21', tom: '2022-09-21' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-22', tom: '2022-09-23' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-26', tom: '2022-09-27' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-28', tom: '2022-09-28' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-09-29', tom: '2022-09-30' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-10-03', tom: '2022-10-07' }, tidPerDag: 'PT0S', kilde: 'SØKER' }, + { periode: { fom: '2022-10-10', tom: '2022-10-11' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-10-12', tom: '2022-10-12' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-10-13', tom: '2022-10-14' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-10-17', tom: '2022-10-18' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-10-19', tom: '2022-10-19' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-10-20', tom: '2022-10-21' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-10-24', tom: '2022-10-25' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-10-26', tom: '2022-10-26' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-10-27', tom: '2022-10-28' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-10-31', tom: '2022-11-01' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-02', tom: '2022-11-02' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-03', tom: '2022-11-04' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-07', tom: '2022-11-08' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-09', tom: '2022-11-09' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-10', tom: '2022-11-11' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-14', tom: '2022-11-15' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-16', tom: '2022-11-16' }, tidPerDag: 'PT0S', kilde: 'SØKER' }, + { periode: { fom: '2022-11-17', tom: '2022-11-18' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-21', tom: '2022-11-22' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-23', tom: '2022-11-23' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-24', tom: '2022-11-25' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-28', tom: '2022-11-29' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-11-30', tom: '2022-11-30' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-12-01', tom: '2022-12-02' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-12-05', tom: '2022-12-06' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-12-07', tom: '2022-12-07' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-12-08', tom: '2022-12-09' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-12-12', tom: '2022-12-13' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-12-14', tom: '2022-12-14' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-12-15', tom: '2022-12-16' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-12-19', tom: '2022-12-20' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2022-12-21', tom: '2022-12-21' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2022-12-22', tom: '2023-01-01' }, tidPerDag: 'PT0S', kilde: 'SØKER' }, + { periode: { fom: '2023-01-02', tom: '2023-01-03' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-04', tom: '2023-01-04' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-05', tom: '2023-01-06' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-09', tom: '2023-01-10' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-11', tom: '2023-01-11' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-12', tom: '2023-01-13' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-16', tom: '2023-01-17' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-18', tom: '2023-01-18' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-19', tom: '2023-01-20' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-23', tom: '2023-01-24' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-25', tom: '2023-01-25' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-26', tom: '2023-01-27' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-01-30', tom: '2023-01-31' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-02-01', tom: '2023-02-01' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-02-02', tom: '2023-02-03' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-02-06', tom: '2023-02-07' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-02-08', tom: '2023-02-08' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-02-09', tom: '2023-02-10' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-02-13', tom: '2023-02-14' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-02-15', tom: '2023-02-15' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-02-16', tom: '2023-02-17' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-02-20', tom: '2023-02-24' }, tidPerDag: 'PT0S', kilde: 'SØKER' }, + { periode: { fom: '2023-02-27', tom: '2023-02-28' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-01', tom: '2023-03-01' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-02', tom: '2023-03-03' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-06', tom: '2023-03-07' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-08', tom: '2023-03-08' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-09', tom: '2023-03-10' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-13', tom: '2023-03-14' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-15', tom: '2023-03-15' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-16', tom: '2023-03-17' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-20', tom: '2023-03-21' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-22', tom: '2023-03-22' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-23', tom: '2023-03-24' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-27', tom: '2023-03-28' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-29', tom: '2023-03-29' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-03-30', tom: '2023-03-31' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-04-03', tom: '2023-04-05' }, tidPerDag: 'PT0S', kilde: 'SØKER' }, + { periode: { fom: '2023-04-06', tom: '2023-04-07' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-04-10', tom: '2023-04-11' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-04-12', tom: '2023-04-12' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-04-13', tom: '2023-04-14' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-04-17', tom: '2023-04-18' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-04-19', tom: '2023-04-19' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-04-20', tom: '2023-04-21' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-04-24', tom: '2023-04-25' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-04-26', tom: '2023-04-26' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-04-27', tom: '2023-04-28' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-01', tom: '2023-05-02' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-03', tom: '2023-05-03' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-04', tom: '2023-05-05' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-08', tom: '2023-05-09' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-10', tom: '2023-05-10' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-11', tom: '2023-05-12' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-15', tom: '2023-05-16' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-17', tom: '2023-05-17' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-18', tom: '2023-05-18' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-19', tom: '2023-05-19' }, tidPerDag: 'PT0S', kilde: 'SØKER' }, + { periode: { fom: '2023-05-22', tom: '2023-05-23' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-24', tom: '2023-05-24' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-25', tom: '2023-05-26' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-29', tom: '2023-05-30' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-05-31', tom: '2023-05-31' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-06-01', tom: '2023-06-02' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-06-05', tom: '2023-06-06' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-06-07', tom: '2023-06-07' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-06-08', tom: '2023-06-09' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-06-12', tom: '2023-06-13' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-06-14', tom: '2023-06-14' }, tidPerDag: 'PT3H', kilde: 'SØKER' }, + { periode: { fom: '2023-06-15', tom: '2023-06-16' }, tidPerDag: 'PT5H', kilde: 'SØKER' }, + { periode: { fom: '2023-06-19', tom: '2023-08-16' }, tidPerDag: 'PT0S', kilde: 'SØKER' }, + ], + smortEtablertTilsynPerioder: [ + { periode: { fom: '2022-08-18', tom: '2022-08-19' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-08-22', tom: '2022-08-23' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-08-24', tom: '2022-08-24' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-08-25', tom: '2022-08-26' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-08-29', tom: '2022-08-30' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-08-31', tom: '2022-08-31' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-09-01', tom: '2022-09-02' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-09-05', tom: '2022-09-06' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-09-07', tom: '2022-09-07' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-09-08', tom: '2022-09-09' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-09-12', tom: '2022-09-13' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-09-14', tom: '2022-09-14' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-09-15', tom: '2022-09-16' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-09-19', tom: '2022-09-20' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-09-21', tom: '2022-09-21' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-09-22', tom: '2022-09-23' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-09-26', tom: '2022-09-27' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-09-28', tom: '2022-09-28' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-09-29', tom: '2022-09-30' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-10-03', tom: '2022-10-07' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2022-10-10', tom: '2022-10-11' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-10-12', tom: '2022-10-12' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-10-13', tom: '2022-10-14' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-10-17', tom: '2022-10-18' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-10-19', tom: '2022-10-19' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-10-20', tom: '2022-10-21' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-10-24', tom: '2022-10-25' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-10-26', tom: '2022-10-26' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-10-27', tom: '2022-10-28' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-10-31', tom: '2022-11-01' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-11-02', tom: '2022-11-02' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-11-03', tom: '2022-11-04' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-11-07', tom: '2022-11-08' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-11-09', tom: '2022-11-09' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-11-10', tom: '2022-11-11' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-11-14', tom: '2022-11-15' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-11-16', tom: '2022-11-16' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2022-11-17', tom: '2022-11-18' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-11-21', tom: '2022-11-22' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-11-23', tom: '2022-11-23' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-11-24', tom: '2022-11-25' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-11-28', tom: '2022-11-29' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-11-30', tom: '2022-11-30' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-12-01', tom: '2022-12-02' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-12-05', tom: '2022-12-06' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-12-07', tom: '2022-12-07' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-12-08', tom: '2022-12-09' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-12-12', tom: '2022-12-13' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-12-14', tom: '2022-12-14' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-12-15', tom: '2022-12-16' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-12-19', tom: '2022-12-20' }, tidPerDag: 'PT5H' }, + { periode: { fom: '2022-12-21', tom: '2022-12-21' }, tidPerDag: 'PT3H' }, + { periode: { fom: '2022-12-22', tom: '2023-01-01' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-01-02', tom: '2023-01-03' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-04', tom: '2023-01-04' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-05', tom: '2023-01-06' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-09', tom: '2023-01-10' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-11', tom: '2023-01-11' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-12', tom: '2023-01-13' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-16', tom: '2023-01-17' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-18', tom: '2023-01-18' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-19', tom: '2023-01-20' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-23', tom: '2023-01-24' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-25', tom: '2023-01-25' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-26', tom: '2023-01-27' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-01-30', tom: '2023-01-31' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-02-01', tom: '2023-02-01' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-02-02', tom: '2023-02-03' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-02-06', tom: '2023-02-07' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-02-08', tom: '2023-02-08' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-02-09', tom: '2023-02-10' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-02-13', tom: '2023-02-14' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-02-15', tom: '2023-02-15' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-02-16', tom: '2023-02-17' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-02-20', tom: '2023-02-24' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-02-27', tom: '2023-02-28' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-01', tom: '2023-03-01' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-02', tom: '2023-03-03' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-06', tom: '2023-03-07' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-08', tom: '2023-03-08' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-09', tom: '2023-03-10' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-13', tom: '2023-03-14' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-15', tom: '2023-03-15' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-16', tom: '2023-03-17' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-20', tom: '2023-03-21' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-22', tom: '2023-03-22' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-23', tom: '2023-03-24' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-27', tom: '2023-03-28' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-29', tom: '2023-03-29' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-03-30', tom: '2023-03-31' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-04-03', tom: '2023-04-05' }, tidPerDag: 'PT2H' }, + { periode: { fom: '2023-04-06', tom: '2023-04-07' }, tidPerDag: 'PT2H' }, + { periode: { fom: '2023-04-10', tom: '2023-04-11' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-04-12', tom: '2023-04-12' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-04-13', tom: '2023-04-14' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-04-17', tom: '2023-04-18' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-04-19', tom: '2023-04-19' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-04-20', tom: '2023-04-21' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-04-24', tom: '2023-04-25' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-04-26', tom: '2023-04-26' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-04-27', tom: '2023-04-28' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-01', tom: '2023-05-02' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-03', tom: '2023-05-03' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-04', tom: '2023-05-05' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-08', tom: '2023-05-09' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-10', tom: '2023-05-10' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-11', tom: '2023-05-12' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-15', tom: '2023-05-16' }, tidPerDag: 'PT3H36M' }, + { periode: { fom: '2023-05-17', tom: '2023-05-17' }, tidPerDag: 'PT3H36M' }, + { periode: { fom: '2023-05-18', tom: '2023-05-18' }, tidPerDag: 'PT3H36M' }, + { periode: { fom: '2023-05-19', tom: '2023-05-19' }, tidPerDag: 'PT3H36M' }, + { periode: { fom: '2023-05-22', tom: '2023-05-23' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-24', tom: '2023-05-24' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-25', tom: '2023-05-26' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-29', tom: '2023-05-30' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-05-31', tom: '2023-05-31' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-06-01', tom: '2023-06-02' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-06-05', tom: '2023-06-06' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-06-07', tom: '2023-06-07' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-06-08', tom: '2023-06-09' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-06-12', tom: '2023-06-13' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-06-14', tom: '2023-06-14' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-06-15', tom: '2023-06-16' }, tidPerDag: 'PT4H36M' }, + { periode: { fom: '2023-06-19', tom: '2023-06-23' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-06-24', tom: '2023-06-25' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-06-26', tom: '2023-06-30' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-07-01', tom: '2023-07-02' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-07-03', tom: '2023-07-07' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-07-08', tom: '2023-07-09' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-07-10', tom: '2023-07-14' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-07-15', tom: '2023-07-16' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-07-17', tom: '2023-07-21' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-07-22', tom: '2023-07-23' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-07-24', tom: '2023-07-28' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-07-29', tom: '2023-07-30' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-07-31', tom: '2023-08-04' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-08-05', tom: '2023-08-06' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-08-07', tom: '2023-08-11' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-08-12', tom: '2023-08-13' }, tidPerDag: 'PT0S' }, + { periode: { fom: '2023-08-14', tom: '2023-08-16' }, tidPerDag: 'PT0S' }, + ], + nattevåk: { + beskrivelser: [ + { + periode: { + fom: '2021-05-11', + tom: '2021-05-11', + }, + tekst: 'string', + mottattDato: '2021-05-11', + kilde: 'SØKER', + }, + ], + vurderinger: [ + { + id: 0, + periode: { + fom: '2021-05-11', + tom: '2021-05-11', + }, + begrunnelse: 'string', + resultat: 'OPPFYLT', + kilde: 'SØKER', + }, + { + id: 1, + periode: { + fom: '2021-06-11', + tom: '2021-06-11', + }, + begrunnelse: 'streng', + resultat: 'IKKE_OPPFYLT', + kilde: 'ANNEN_PART', + }, + { + id: 2, + periode: { + fom: '2021-07-11', + tom: '2021-07-12', + }, + begrunnelse: '', + resultat: 'IKKE_VURDERT', + kilde: '', + }, + ], + }, + beredskap: { + beskrivelser: [ + { + periode: { + fom: '2021-05-11', + tom: '2021-05-11', + }, + tekst: 'string', + mottattDato: '2021-05-11', + kilde: 'SØKER', + }, + ], + vurderinger: [ + { + id: 0, + periode: { + fom: '2021-05-11', + tom: '2021-05-11', + }, + begrunnelse: 'string', + resultat: 'OPPFYLT', + kilde: 'SØKER', + }, + { + id: 1, + periode: { + fom: '2021-06-11', + tom: '2021-06-11', + }, + begrunnelse: 'streng', + resultat: 'IKKE_OPPFYLT', + kilde: 'ANNEN_PART', + }, + { + id: 2, + periode: { + fom: '2021-07-11', + tom: '2021-07-12', + }, + begrunnelse: '', + resultat: 'IKKE_VURDERT', + kilde: '', + }, + ], + }, +}; diff --git a/packages/fakta-etablert-tilsyn/package.json b/packages/fakta-etablert-tilsyn/package.json new file mode 100644 index 0000000000..abef2d138b --- /dev/null +++ b/packages/fakta-etablert-tilsyn/package.json @@ -0,0 +1,57 @@ +{ + "name": "@k9-sak-web/fakta-etablert-tilsyn", + "version": "1.0.0", + "module": "index.ts", + "keywords": [], + "author": "NAV IT", + "license": "MIT", + "private": true, + "scripts": { + "dev": "node webpack/webpack-config.development.js" + }, + "dependencies": { + "@navikt/ds-css": "5.11.2", + "@navikt/ds-icons": "3.4.3", + "@navikt/ds-react": "5.11.2", + "@navikt/fnrvalidator": "1.3.3", + "@navikt/ft-plattform-komponenter": "2.3.14", + "@popperjs/core": "2.11.8", + "axios": "1.6.2", + "classnames": "2.3.2", + "dayjs": "1.11.10", + "lodash": "4.17.21", + "lodash.throttle": "4.1.1", + "nav-frontend-alertstriper": "4.0.2", + "nav-frontend-alertstriper-style": "3.0.2", + "nav-frontend-chevron": "1.0.30", + "nav-frontend-chevron-style": "1.0.4", + "nav-frontend-core": "6.0.1", + "nav-frontend-etiketter": "3.0.3", + "nav-frontend-etiketter-style": "2.0.3", + "nav-frontend-ikoner-assets": "3.0.1", + "nav-frontend-ikonknapper": "2.1.3", + "nav-frontend-js-utils": "1.0.20", + "nav-frontend-knapper": "3.1.3", + "nav-frontend-knapper-style": "2.1.2", + "nav-frontend-lenkepanel": "2.0.2", + "nav-frontend-lenkepanel-style": "2.0.2", + "nav-frontend-paneler": "3.0.2", + "nav-frontend-paneler-style": "2.0.2", + "nav-frontend-skjema": "4.0.6", + "nav-frontend-skjema-style": "3.0.3", + "nav-frontend-spinner": "3.0.1", + "nav-frontend-spinner-style": "1.0.2", + "nav-frontend-tabs": "2.0.3", + "nav-frontend-tabs-style": "2.0.2", + "nav-frontend-typografi": "4.0.2", + "nav-frontend-typografi-style": "2.0.2", + "react": "18.2.0", + "react-collapse": "5.1.1", + "react-dom": "18.2.0", + "react-hook-form": "7.48.2", + "react-popper": "2.3.0" + }, + "msw": { + "workerDirectory": "public" + } +} diff --git a/packages/fakta-etablert-tilsyn/public/mockServiceWorker.js b/packages/fakta-etablert-tilsyn/public/mockServiceWorker.js new file mode 100644 index 0000000000..95835ef353 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/public/mockServiceWorker.js @@ -0,0 +1,302 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (1.3.2). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'; +const activeClientIds = new Set(); + +self.addEventListener('install', function () { + self.skipWaiting(); +}); + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener('message', async function (event) { + const clientId = event.source.id; + + if (!clientId || !self.clients) { + return; + } + + const client = await self.clients.get(clientId); + + if (!client) { + return; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }); + break; + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }); + break; + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId); + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }); + break; + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId); + break; + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId); + + const remainingClients = allClients.filter(client => { + return client.id !== clientId; + }); + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister(); + } + + break; + } + } +}); + +self.addEventListener('fetch', function (event) { + const { request } = event; + const accept = request.headers.get('accept') || ''; + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return; + } + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return; + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return; + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return; + } + + // Generate unique request ID. + const requestId = Math.random().toString(16).slice(2); + + event.respondWith( + handleRequest(event, requestId).catch(error => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ); + return; + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ); + }), + ); +}); + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event); + const response = await getResponse(event, client, requestId); + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + (async function () { + const clonedResponse = response.clone(); + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: clonedResponse.body === null ? null : await clonedResponse.text(), + headers: Object.fromEntries(clonedResponse.headers.entries()), + redirected: clonedResponse.redirected, + }, + }); + })(); + } + + return response; +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId); + + if (client?.frameType === 'top-level') { + return client; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + return allClients + .filter(client => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible'; + }) + .find(client => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id); + }); +} + +async function getResponse(event, client, requestId) { + const { request } = event; + const clonedRequest = request.clone(); + + function passthrough() { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const headers = Object.fromEntries(clonedRequest.headers.entries()); + + // Remove MSW-specific request headers so the bypassed requests + // comply with the server's CORS preflight check. + // Operate with the headers as an object because request "Headers" + // are immutable. + delete headers['x-msw-bypass']; + + return fetch(clonedRequest, { headers }); + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough(); + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough(); + } + + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough(); + } + + // Notify the client that a request has been intercepted. + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.text(), + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }); + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data); + } + + case 'MOCK_NOT_FOUND': { + return passthrough(); + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.data; + const networkError = new Error(message); + networkError.name = name; + + // Rejecting a "respondWith" promise emulates a network error. + throw networkError; + } + } + + return passthrough(); +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel(); + + channel.port1.onmessage = event => { + if (event.data && event.data.error) { + return reject(event.data.error); + } + + resolve(event.data); + }; + + client.postMessage(message, [channel.port2]); + }); +} + +function sleep(timeMs) { + return new Promise(resolve => { + setTimeout(resolve, timeMs); + }); +} + +async function respondWithMock(response) { + await sleep(response.delay); + return new Response(response.body, response); +} diff --git a/packages/fakta-etablert-tilsyn/src/constants/dateConstants.ts b/packages/fakta-etablert-tilsyn/src/constants/dateConstants.ts new file mode 100644 index 0000000000..1cdcbb3994 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/constants/dateConstants.ts @@ -0,0 +1,9 @@ +import dayjs from 'dayjs'; + +export const today = dayjs().utc(true).startOf('day'); +export const tomorrow = today.add(1, 'day').startOf('day'); + +export default { + today, + tomorrow, +}; diff --git a/packages/fakta-etablert-tilsyn/src/dev/app.ts b/packages/fakta-etablert-tilsyn/src/dev/app.ts new file mode 100644 index 0000000000..93e4376bd4 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/dev/app.ts @@ -0,0 +1,8 @@ +import renderers from '../util/renderers'; +import '@navikt/ft-plattform-komponenter/dist/style.css'; +import '@navikt/ds-css'; + +(window as any).renderTilsynApp = async (appId, data) => { + const { renderAppInSuccessfulState } = renderers; + renderAppInSuccessfulState(appId, data); +}; diff --git a/packages/fakta-etablert-tilsyn/src/mock/browser.ts b/packages/fakta-etablert-tilsyn/src/mock/browser.ts new file mode 100644 index 0000000000..507d351d1b --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/mock/browser.ts @@ -0,0 +1,6 @@ +/* eslint-disable import/prefer-default-export */ +/* eslint-disable import/no-extraneous-dependencies */ +import { setupWorker } from 'msw'; +import { handlers } from '../../mock/api-mock'; +// This configures a Service Worker with the given request handlers. +export const worker = setupWorker(...handlers); diff --git a/packages/fakta-etablert-tilsyn/src/mock/etablertTilsynMock.ts b/packages/fakta-etablert-tilsyn/src/mock/etablertTilsynMock.ts new file mode 100644 index 0000000000..ece764f656 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/mock/etablertTilsynMock.ts @@ -0,0 +1,352 @@ +import { Period } from '@fpsak-frontend/utils'; +import EtablertTilsynType from '../types/EtablertTilsynType'; + +const mapTilEtablertTilsynType = v => new EtablertTilsynType(v); +const mapPeriode = v => new Period(v.fom, v.tom); + +export const toPerioderSammeUke = () => { + const etablertTilsynData = [ + { + periode: { + fom: '2022-09-05', + tom: '2022-09-06', + }, + tidPerDag: 'PT2H', + kilde: 'SØKER', + }, + { + periode: { + fom: '2022-09-07', + tom: '2022-09-08', + }, + tidPerDag: 'PT5H', + kilde: 'SØKER', + }, + { + periode: { + fom: '2022-09-09', + tom: '2022-09-09', + }, + tidPerDag: 'PT6H', + kilde: 'SØKER', + }, + ].map(mapTilEtablertTilsynType); + + const smurtEtablertTilsynPerioder = [ + { + periode: { + fom: '2022-09-05', + tom: '2022-09-05', + }, + tidPerDag: 'PT2H36M', + }, + { + periode: { + fom: '2022-09-06', + tom: '2022-09-06', + }, + tidPerDag: 'PT2H36M', + }, + { + periode: { + fom: '2022-09-07', + tom: '2022-09-07', + }, + tidPerDag: 'PT2H36M', + }, + { + periode: { + fom: '2022-09-08', + tom: '2022-09-08', + }, + tidPerDag: 'PT2H36M', + }, + { + periode: { + fom: '2022-09-09', + tom: '2022-09-09', + }, + tidPerDag: 'PT2H36M', + }, + ].map(mapTilEtablertTilsynType); + + const avslaattePerioder = [ + { + fom: '2022-09-08', + tom: '2022-09-08', + }, + ].map(mapPeriode); + + const innleggelsesperioder = [].map(mapPeriode); + + return { etablertTilsynData, smurtEtablertTilsynPerioder, avslaattePerioder, innleggelsesperioder }; +}; +export const dagOverstyrt = () => { + const etablertTilsynData = [ + { + periode: { + fom: '2022-09-05', + tom: '2022-09-06', + }, + tidPerDag: 'PT2H', + kilde: 'SØKER', + }, + { + periode: { + fom: '2022-09-07', + tom: '2022-09-08', + }, + tidPerDag: 'PT5H', + kilde: 'SØKER', + }, + { + periode: { + fom: '2022-09-09', + tom: '2022-09-09', + }, + tidPerDag: 'PT6H', + kilde: 'SØKER', + }, + ].map(mapTilEtablertTilsynType); + + const smurtEtablertTilsynPerioder = [ + { + periode: { + fom: '2022-09-05', + tom: '2022-09-05', + }, + tidPerDag: 'PT2H36M', + }, + { + periode: { + fom: '2022-09-06', + tom: '2022-09-06', + }, + tidPerDag: 'PT2H36M', + }, + { + periode: { + fom: '2022-09-07', + tom: '2022-09-07', + }, + tidPerDag: 'PT2H36M', + }, + { + periode: { + fom: '2022-09-08', + tom: '2022-09-08', + }, + tidPerDag: 'PT2H36M', + }, + { + periode: { + fom: '2022-09-09', + tom: '2022-09-09', + }, + tidPerDag: 'PT2H36M', + }, + ].map(mapTilEtablertTilsynType); + + const avslaattePerioder = [].map(mapPeriode); + + const innleggelsesperioder = [ + { + fom: '2022-09-08', + tom: '2022-09-08', + }, + ].map(mapPeriode); + + return { etablertTilsynData, smurtEtablertTilsynPerioder, avslaattePerioder, innleggelsesperioder }; +}; + +export const treEnkeltdager = () => { + const etablertTilsynData = [ + { + periode: { + fom: '2022-09-19', + tom: '2022-09-19', + }, + tidPerDag: 'PT5H30M', + kilde: 'SØKER', + }, + { + periode: { + fom: '2022-09-20', + tom: '2022-09-20', + }, + tidPerDag: 'PT0H', + kilde: 'SØKER', + }, + { + periode: { + fom: '2022-09-21', + tom: '2022-09-21', + }, + tidPerDag: 'PT5H30M', + kilde: 'SØKER', + }, + { + periode: { + fom: '2022-09-22', + tom: '2022-09-22', + }, + tidPerDag: 'PT0H', + kilde: 'SØKER', + }, + { + periode: { + fom: '2022-09-23', + tom: '2022-09-23', + }, + tidPerDag: 'PT5H30M', + kilde: 'SØKER', + }, + ].map(mapTilEtablertTilsynType); + + const smurtEtablertTilsynPerioder = [ + { + periode: { + fom: '2022-09-19', + tom: '2022-09-19', + }, + tidPerDag: 'PT5H30M', + }, + { + periode: { + fom: '2022-09-20', + tom: '2022-09-20', + }, + tidPerDag: 'PT0H', + }, + { + periode: { + fom: '2022-09-21', + tom: '2022-09-21', + }, + tidPerDag: 'PT5H30M', + }, + { + periode: { + fom: '2022-09-22', + tom: '2022-09-22', + }, + tidPerDag: 'PT0H', + }, + { + periode: { + fom: '2022-09-23', + tom: '2022-09-23', + }, + tidPerDag: 'PT5H30M', + }, + ].map(mapTilEtablertTilsynType); + + const avslaattePerioder = [ + { + fom: '2022-09-20', + tom: '2022-09-20', + }, + { + fom: '2022-09-22', + tom: '2022-09-22', + }, + ].map(mapPeriode); + + return { etablertTilsynData, smurtEtablertTilsynPerioder, avslaattePerioder, innleggelsesperioder: [] }; +}; + +export const toSmøringer = () => { + const etablertTilsynData = [ + { + periode: { + fom: '2022-10-03', + tom: '2022-10-03', + }, + tidPerDag: 'PT7H', + kilde: 'SØKER', + }, + { + periode: { + fom: '2022-10-04', + tom: '2022-10-04', + }, + tidPerDag: 'PT1H30M', + kilde: 'ANDRE', + }, + { + periode: { + fom: '2022-10-05', + tom: '2022-10-05', + }, + tidPerDag: 'PT4H33M', + kilde: 'SØKER', + }, + { + periode: { + fom: '2022-10-06', + tom: '2022-10-06', + }, + tidPerDag: 'PT2H33M', + kilde: 'ANDRE', + }, + { + periode: { + fom: '2022-10-07', + tom: '2022-10-07', + }, + tidPerDag: 'PT6H', + kilde: 'SØKER', + }, + ].map(mapTilEtablertTilsynType); + + const smurtEtablertTilsynPerioder = [ + { + periode: { + fom: '2022-10-03', + tom: '2022-10-03', + }, + tidPerDag: 'PT4H15M', + }, + { + periode: { + fom: '2022-10-04', + tom: '2022-10-04', + }, + tidPerDag: 'PT4H15M', + }, + { + periode: { + fom: '2022-10-05', + tom: '2022-10-05', + }, + tidPerDag: 'PT4H33M', + }, + { + periode: { + fom: '2022-10-06', + tom: '2022-10-06', + }, + tidPerDag: 'PT4H16M30S', + }, + { + periode: { + fom: '2022-10-07', + tom: '2022-10-07', + }, + tidPerDag: 'PT4H16M30S', + }, + ].map(mapTilEtablertTilsynType); + + const avslaattePerioder = [ + { + id: '999999', + resultat: 'IKKE_OPPFYLT', + periode: { + fom: '2022-10-05', + tom: '2022-10-05', + }, + }, + ].map(mapPeriode); + + return { etablertTilsynData, smurtEtablertTilsynPerioder, avslaattePerioder, innleggelsesperioder: [] }; +}; diff --git a/packages/fakta-etablert-tilsyn/src/tests/etablertTilsynMedSmoring.spec.tsx b/packages/fakta-etablert-tilsyn/src/tests/etablertTilsynMedSmoring.spec.tsx new file mode 100644 index 0000000000..37bd66179f --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/tests/etablertTilsynMedSmoring.spec.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import EtablertTilsynMedSmoring from '../ui/components/etablertTilsyn/EtablertTilsynMedSmoring'; +import { toPerioderSammeUke, toSmøringer, treEnkeltdager, dagOverstyrt } from '../mock/etablertTilsynMock'; + +describe('Etablert tilsyn med smøring', () => { + test('Kan vise etablert tilsyn med to smøringer', () => { + const data = toPerioderSammeUke(); + render( + , + ); + + expect(screen.getByText('Uke 37 - A')).toBeDefined(); + expect(screen.getByText('Uke 37 - B')).toBeDefined(); + }); + test('Kan vise etablert tilsyn med tre enkeltdager', async () => { + const data = treEnkeltdager(); + render( + , + ); + + expect(screen.getByText('Uke 39 - A')).toBeDefined(); + expect(screen.getByText('Uke 39 - B')).toBeDefined(); + expect(screen.getByText('Uke 39 - C')).toBeDefined(); + + expect(screen.getAllByText('= 5.5 t per dag (73%)').length).toEqual(3); + }); + test('Kan vise etablert tilsyn med to smøringer og forskjellig prosent', async () => { + const data = toSmøringer(); + render( + , + ); + + expect(screen.getByText('Uke 41 - A')).toBeDefined(); + expect(screen.getByText('Uke 41 - B')).toBeDefined(); + + expect(screen.getByText('= 4.25 t per dag (57%)')).toBeDefined(); + expect(screen.getByText('= 4.28 t per dag (57%)')).toBeDefined(); + }); + test('Uke med innleggelse skal ikke deles opp', async () => { + const data = dagOverstyrt(); + render( + , + ); + + expect(screen.getByText('Uke 37')).toBeDefined(); + expect(screen.queryByText('Uke 37 - B')); + + expect(screen.getByText('= 2.6 t per dag (35%)')).toBeDefined(); + }); +}); diff --git a/packages/fakta-etablert-tilsyn/src/types/BeredskapType.ts b/packages/fakta-etablert-tilsyn/src/types/BeredskapType.ts new file mode 100644 index 0000000000..e748650a80 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/types/BeredskapType.ts @@ -0,0 +1,37 @@ +import { Period } from '@fpsak-frontend/utils'; +import Beskrivelse from './Beskrivelse'; +import Vurderingsperiode from './Vurderingsperiode'; +import { Beredskap } from './TilsynResponse'; + +class BeredskapType { + beskrivelser: Beskrivelse[]; + + vurderinger: Vurderingsperiode[]; + + constructor({ beskrivelser, vurderinger }: Beredskap) { + this.beskrivelser = beskrivelser.map(beskrivelse => ({ + ...beskrivelse, + periode: new Period(beskrivelse.periode.fom, beskrivelse.periode.tom), + })); + + this.vurderinger = vurderinger.map(vurdering => new Vurderingsperiode(vurdering)); + } + + finnPerioderTilVurdering() { + return this.vurderinger.filter(vurdering => vurdering.skalVurderes()); + } + + finnVurdertePerioder() { + return this.vurderinger.filter(vurdering => vurdering.erVurdert()); + } + + harPerioderTilVurdering() { + return this.vurderinger.some(vurdering => vurdering.skalVurderes()); + } + + harPerioder() { + return this.vurderinger.length > 0; + } +} + +export default BeredskapType; diff --git a/packages/fakta-etablert-tilsyn/src/types/Beskrivelse.ts b/packages/fakta-etablert-tilsyn/src/types/Beskrivelse.ts new file mode 100644 index 0000000000..12a2171ec3 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/types/Beskrivelse.ts @@ -0,0 +1,11 @@ +import { Period } from '@fpsak-frontend/utils'; +import Kilde from './Kilde'; + +interface Beskrivelse { + periode: Period; + tekst: string; + mottattDato: string; + kilde: Kilde; +} + +export default Beskrivelse; diff --git a/packages/fakta-etablert-tilsyn/src/types/ContainerContract.ts b/packages/fakta-etablert-tilsyn/src/types/ContainerContract.ts new file mode 100644 index 0000000000..3f301b7ef1 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/types/ContainerContract.ts @@ -0,0 +1,18 @@ +import Saksbehandlere from './Saksbehandlere'; + +interface ContainerContract { + readOnly: boolean; + endpoints: { + tilsyn: string; + sykdom: string; + sykdomInnleggelse: string; + }; + httpErrorHandler: (statusCode: number, locationHeader?: string) => void; + lagreBeredskapvurdering: (data: any) => void; + lagreNattevåkvurdering: (data: any) => void; + harAksjonspunktForBeredskap: boolean; + harAksjonspunktForNattevåk: boolean; + saksbehandlere: Saksbehandlere; +} + +export default ContainerContract; diff --git a/packages/fakta-etablert-tilsyn/src/types/EtablertTilsynType.ts b/packages/fakta-etablert-tilsyn/src/types/EtablertTilsynType.ts new file mode 100644 index 0000000000..8279b1b41e --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/types/EtablertTilsynType.ts @@ -0,0 +1,20 @@ +import { Period } from '@fpsak-frontend/utils'; +import { EtablertTilsynPeriode } from './TilsynResponse'; +import { beregnDagerTimer } from '../util/dateUtils'; +import Kilde from './Kilde'; + +class EtablertTilsynType { + periode: Period; + + tidPerDag: number; + + kilde: Kilde; + + constructor({ periode, tidPerDag, kilde }: EtablertTilsynPeriode) { + this.periode = new Period(periode.fom, periode.tom); + this.tidPerDag = beregnDagerTimer(tidPerDag); + this.kilde = kilde; + } +} + +export default EtablertTilsynType; diff --git a/packages/fakta-etablert-tilsyn/src/types/Kilde.ts b/packages/fakta-etablert-tilsyn/src/types/Kilde.ts new file mode 100644 index 0000000000..db9be12ee7 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/types/Kilde.ts @@ -0,0 +1,6 @@ +enum Kilde { + SØKER = 'SØKER', + ANDRE = 'ANDRE', +} + +export default Kilde; diff --git "a/packages/fakta-etablert-tilsyn/src/types/Nattev\303\245kType.ts" "b/packages/fakta-etablert-tilsyn/src/types/Nattev\303\245kType.ts" new file mode 100644 index 0000000000..20eaafd465 --- /dev/null +++ "b/packages/fakta-etablert-tilsyn/src/types/Nattev\303\245kType.ts" @@ -0,0 +1,37 @@ +import { Period } from '@fpsak-frontend/utils'; +import Beskrivelse from './Beskrivelse'; +import Vurderingsperiode from './Vurderingsperiode'; +import { Nattevåk } from './TilsynResponse'; + +class NattevåkType { + beskrivelser: Beskrivelse[]; + + vurderinger: Vurderingsperiode[]; + + constructor({ beskrivelser, vurderinger }: Nattevåk) { + this.beskrivelser = beskrivelser.map(beskrivelse => ({ + ...beskrivelse, + periode: new Period(beskrivelse.periode.fom, beskrivelse.periode.tom), + })); + + this.vurderinger = vurderinger.map(vurdering => new Vurderingsperiode(vurdering)); + } + + finnPerioderTilVurdering() { + return this.vurderinger.filter(vurdering => vurdering.skalVurderes()); + } + + finnVurdertePerioder() { + return this.vurderinger.filter(vurdering => vurdering.erVurdert()); + } + + harPerioderTilVurdering() { + return this.vurderinger.some(vurdering => vurdering.skalVurderes()); + } + + harPerioder() { + return this.vurderinger.length > 0; + } +} + +export default NattevåkType; diff --git a/packages/fakta-etablert-tilsyn/src/types/Saksbehandlere.ts b/packages/fakta-etablert-tilsyn/src/types/Saksbehandlere.ts new file mode 100644 index 0000000000..99e4e4509a --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/types/Saksbehandlere.ts @@ -0,0 +1,3 @@ +type Saksbehandlere = { [saksbehandlerId: string]: string }; + +export default Saksbehandlere; diff --git a/packages/fakta-etablert-tilsyn/src/types/TilsynResponse.ts b/packages/fakta-etablert-tilsyn/src/types/TilsynResponse.ts new file mode 100644 index 0000000000..45e02ad322 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/types/TilsynResponse.ts @@ -0,0 +1,68 @@ +import { Period } from '@fpsak-frontend/utils'; +import Kilde from './Kilde'; +import Vurderingsresultat from './Vurderingsresultat'; + +export interface TilsynResponse { + etablertTilsynPerioder: EtablertTilsynPeriode[]; + nattevåk: Nattevåk; + beredskap: Beredskap; + smortEtablertTilsynPerioder: EtablertTilsynPeriode[]; +} + +export interface Vurderingselementer { + periode: { + fom: string; + tom: string; + }; + resultat: string; +} +export interface SykdomResponse { + vurderingselementer: Vurderingselementer[]; + resterendeVurderingsperioder: { + fom: string; + tom: string; + }[]; +} +export interface InnleggelsesperiodeResponse { + behandlingUuid: string; + versjon: string; + perioder: Period[]; +} + +export interface Beredskap { + beskrivelser: Beskrivelse[]; + vurderinger: Vurdering[]; +} + +export interface Nattevåk { + beskrivelser: Beskrivelse[]; + vurderinger: Vurdering[]; +} + +interface Beskrivelse { + periode: Periode; + tekst: string; + mottattDato: string; + kilde: Kilde; +} + +interface Periode { + fom: string; + tom: string; +} + +export interface Vurdering { + id: number; + periode: Periode; + begrunnelse: string; + resultat: Vurderingsresultat; + kilde: Kilde; + opprettetAv: string; + opprettetTidspunkt: string; +} + +export interface EtablertTilsynPeriode { + periode: Periode; + tidPerDag: string; + kilde: Kilde; +} diff --git a/packages/fakta-etablert-tilsyn/src/types/Vurderingsperiode.ts b/packages/fakta-etablert-tilsyn/src/types/Vurderingsperiode.ts new file mode 100644 index 0000000000..7c9f9fac8f --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/types/Vurderingsperiode.ts @@ -0,0 +1,40 @@ +import { Period } from '@fpsak-frontend/utils'; +import Kilde from './Kilde'; +import { Vurdering } from './TilsynResponse'; +import Vurderingsresultat from './Vurderingsresultat'; + +class Vurderingsperiode { + periode: Period; + + kilde: Kilde; + + resultat: Vurderingsresultat | null; + + begrunnelse: string; + + id: number; + + opprettetAv: string; + + opprettetTidspunkt: string; + + constructor({ periode, kilde, resultat, begrunnelse, id, opprettetAv, opprettetTidspunkt }: Vurdering) { + this.periode = new Period(periode.fom, periode.tom); + this.kilde = kilde; + this.resultat = resultat; + this.begrunnelse = begrunnelse; + this.id = id; + this.opprettetAv = opprettetAv; + this.opprettetTidspunkt = opprettetTidspunkt; + } + + skalVurderes() { + return this.resultat === Vurderingsresultat.IKKE_VURDERT; + } + + erVurdert() { + return this.resultat === Vurderingsresultat.OPPFYLT || this.resultat === Vurderingsresultat.IKKE_OPPFYLT; + } +} + +export default Vurderingsperiode; diff --git a/packages/fakta-etablert-tilsyn/src/types/Vurderingsresultat.ts b/packages/fakta-etablert-tilsyn/src/types/Vurderingsresultat.ts new file mode 100644 index 0000000000..72915f2048 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/types/Vurderingsresultat.ts @@ -0,0 +1,7 @@ +enum Vurderingsresultat { + OPPFYLT = 'OPPFYLT', + IKKE_OPPFYLT = 'IKKE_OPPFYLT', + IKKE_VURDERT = 'IKKE_VURDERT', +} + +export default Vurderingsresultat; diff --git a/packages/fakta-etablert-tilsyn/src/ui/MainComponent.tsx b/packages/fakta-etablert-tilsyn/src/ui/MainComponent.tsx new file mode 100644 index 0000000000..a221cca943 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/MainComponent.tsx @@ -0,0 +1,184 @@ +import { get, Period } from '@fpsak-frontend/utils'; +import { ChildIcon, Infostripe, PageContainer, WarningIcon } from '@navikt/ft-plattform-komponenter'; +import classnames from 'classnames'; +import { TabsPure } from 'nav-frontend-tabs'; +import React, { useMemo } from 'react'; +import ContainerContract from '../types/ContainerContract'; +import { InnleggelsesperiodeResponse, SykdomResponse, TilsynResponse } from '../types/TilsynResponse'; +import Alertstripe from './components/alertstripe/Alertstripe'; +import Beredskapsperiodeoversikt from './components/beredskap/beredskapsperioderoversikt/Beredskapsperiodeoversikt'; +import EtablertTilsyn from './components/etablertTilsyn/EtablertTilsynMedSmoring'; +import Nattevåksperiodeoversikt from './components/nattevåk/nattevåksperiodeoversikt/Nattevåksperiodeoversikt'; +import ContainerContext from './context/ContainerContext'; +import ActionType from './mainActionTypes'; +import styles from './mainComponent.css'; +import mainComponentReducer from './mainReducer'; + +interface MainComponentProps { + data: ContainerContract; +} + +const tabs = ['Etablert tilsyn', 'Beredskap', 'Nattevåk']; + +interface TabItemProps { + label: string; + showWarningIcon: boolean; +} + +const TabItem = ({ label, showWarningIcon }: TabItemProps) => { + const cls = classnames(styles.tabItem, { + [styles.tabItemExtended]: showWarningIcon, + }); + return ( +
+ {label} + {showWarningIcon && ( +
+ +
+ )} +
+ ); +}; + +const setDefaultActiveTabIndex = ({ harAksjonspunktForBeredskap, harAksjonspunktForNattevåk }: ContainerContract) => { + if (harAksjonspunktForBeredskap) { + return 1; + } + if (harAksjonspunktForNattevåk) { + return 2; + } + return 0; +}; + +const MainComponent = ({ data }: MainComponentProps) => { + const [state, dispatch] = React.useReducer(mainComponentReducer, { + isLoading: true, + etablertTilsyn: null, + beredskap: null, + nattevåk: null, + sykdomsperioderSomIkkeErOppfylt: [], + }); + const { + isLoading, + etablertTilsyn, + smurtEtablertTilsynPerioder, + beredskap, + nattevåk, + sykdomsperioderSomIkkeErOppfylt, + tilsynHarFeilet, + sykdomHarFeilet, + } = state; + const { endpoints, httpErrorHandler, harAksjonspunktForBeredskap, harAksjonspunktForNattevåk } = data; + const [activeTab, setActiveTab] = React.useState(setDefaultActiveTabIndex(data)); + const [innleggelsesperioder, setInnleggelsesperioder] = React.useState([]); + const [innleggelserFeilet, setInnleggelserFeilet] = React.useState(false); + const controller = useMemo(() => new AbortController(), []); + const getTilsyn = () => + get(endpoints.tilsyn, httpErrorHandler, { + signal: controller.signal, + }); + const getSykdom = () => + get(endpoints.sykdom, httpErrorHandler, { + signal: controller.signal, + }); + const getInnleggelser = () => + get(endpoints.sykdomInnleggelse, httpErrorHandler, { + signal: controller.signal, + }); + + React.useEffect(() => { + let isMounted = true; + getTilsyn() + .then(tilsynResponse => { + if (isMounted) { + dispatch({ type: ActionType.OK, tilsynResponse }); + } + }) + .catch(() => { + dispatch({ type: ActionType.FAILED }); + }); + getSykdom() + .then(sykdomResponse => { + if (isMounted) { + dispatch({ type: ActionType.SYKDOM_OK, sykdomResponse }); + } + }) + .catch(() => { + dispatch({ type: ActionType.SYKDOM_FAILED }); + }); + + getInnleggelser() + .then(innleggelserResponse => { + if (isMounted) { + setInnleggelsesperioder(innleggelserResponse.perioder.map(v => new Period(v.fom, v.tom))); + } + }) + .catch(() => { + setInnleggelserFeilet(true); + }); + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + const bedredskapVurderinger = beredskap?.vurderinger || []; + const nattevåkVurderinger = nattevåk?.vurderinger || []; + const perioderSomOverstyrerTilsyn = [ + ...bedredskapVurderinger.filter(v => v.resultat === 'OPPFYLT').map(v => new Period(v.periode.fom, v.periode.tom)), + ...nattevåkVurderinger.filter(v => v.resultat === 'OPPFYLT').map(v => new Period(v.periode.fom, v.periode.tom)), + ...innleggelsesperioder, + ]; + + if (tilsynHarFeilet || sykdomHarFeilet || innleggelserFeilet) { + return ( + + Noe gikk galt under henting av informasjon om etablert tilsyn. Dette kan skyldes at informasjon om etablert + tilsyn ikke er tilgjengelig ennå, og at andre steg i behandlingen må fullføres før de kan vises her. + + ); + } + + return ( + + } + /> +
+ ({ + label: ( + + ), + aktiv: activeTab === index, + }))} + onChange={(event, clickedIndex) => setActiveTab(clickedIndex)} + /> + +
+ {activeTab === 0 && ( + + )} + {activeTab === 1 && } + {activeTab === 2 && } +
+
+
+
+ ); +}; + +export default MainComponent; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/add-button/AddButton.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/add-button/AddButton.tsx new file mode 100644 index 0000000000..495f75e76e --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/add-button/AddButton.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { PlusIcon } from '@navikt/ft-plattform-komponenter'; +import styles from './addButton.css'; + +interface AddButtonProps { + onClick: () => void; + label: string; + id?: string; + className?: string; +} + +const AddButton = ({ className, label, onClick, id }: AddButtonProps) => ( + +); + +export default AddButton; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/add-button/addButton.css b/packages/fakta-etablert-tilsyn/src/ui/components/add-button/addButton.css new file mode 100644 index 0000000000..afab025618 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/add-button/addButton.css @@ -0,0 +1,15 @@ +.addButton { + display: flex; + padding: 0; + color: #0067c5; + border: none; + background: none; + height: 23px; + position: relative; + cursor: pointer; + align-items: center; +} + +.addButton__text { + margin-left: 0.5rem; +} diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/alertstripe/Alertstripe.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/alertstripe/Alertstripe.tsx new file mode 100644 index 0000000000..bfe050e567 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/alertstripe/Alertstripe.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import Alertstripe, { AlertStripeType } from 'nav-frontend-alertstriper'; +import styles from './alertstripe.css'; + +interface CustomAlertstripeProps { + type: AlertStripeType; + children: React.ReactNode; +} + +const CustomAlertstripe = ({ type, children }: CustomAlertstripeProps) => ( + + {children} + +); + +export default CustomAlertstripe; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/alertstripe/alertstripe.css b/packages/fakta-etablert-tilsyn/src/ui/components/alertstripe/alertstripe.css new file mode 100644 index 0000000000..96576d03b7 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/alertstripe/alertstripe.css @@ -0,0 +1,3 @@ +.customAlertstripe :global .alertstripe__tekst { + max-width: 50rem; +} diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiode-vurderingsdetaljer/BeredskapsperiodeVurderingsdetaljer.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiode-vurderingsdetaljer/BeredskapsperiodeVurderingsdetaljer.tsx new file mode 100644 index 0000000000..5308d6df20 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiode-vurderingsdetaljer/BeredskapsperiodeVurderingsdetaljer.tsx @@ -0,0 +1,61 @@ +import { Box, Margin, DetailView, LabelledContent, LinkButton, AssessedBy } from '@navikt/ft-plattform-komponenter'; +import React, { useContext } from 'react'; +import Beskrivelse from '../../../../types/Beskrivelse'; +import Vurderingsperiode from '../../../../types/Vurderingsperiode'; +import Vurderingsresultat from '../../../../types/Vurderingsresultat'; +import ContainerContext from '../../../context/ContainerContext'; +import BeskrivelserForPerioden from '../../beskrivelser-for-perioden/BeskrivelserForPerioden'; +import WriteAccessBoundContent from '../../write-access-bound-content/WriteAccessBoundContent'; +import styles from './beredskapsperiodeVurderingsdetaljer.css'; + +interface BeredskapsperiodeVurderingsdetaljerProps { + beredskapsperiode: Vurderingsperiode; + onEditClick: () => void; + beskrivelser: Beskrivelse[]; +} + +const BeredskapsperiodeVurderingsdetaljer = ({ + beredskapsperiode, + onEditClick, + beskrivelser, +}: BeredskapsperiodeVurderingsdetaljerProps): JSX.Element => { + const { saksbehandlere } = useContext(ContainerContext); + const { opprettetAv, opprettetTidspunkt } = beredskapsperiode; + return ( + ( + ( + + Rediger vurdering + + )} + /> + )} + > + + + + + + + + + + + + + + + ); +}; + +export default BeredskapsperiodeVurderingsdetaljer; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiode-vurderingsdetaljer/beredskapsperiodeVurderingsdetaljer.css b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiode-vurderingsdetaljer/beredskapsperiodeVurderingsdetaljer.css new file mode 100644 index 0000000000..9411969476 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiode-vurderingsdetaljer/beredskapsperiodeVurderingsdetaljer.css @@ -0,0 +1,3 @@ +.endreLink { + margin-left: 1rem; +} diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiodeoversikt-controller/BeredskapsperiodeoversiktController.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiodeoversikt-controller/BeredskapsperiodeoversiktController.tsx new file mode 100644 index 0000000000..53297b926b --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiodeoversikt-controller/BeredskapsperiodeoversiktController.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import Vurderingsperiode from '../../../../types/Vurderingsperiode'; +import BeredskapsperiodeVurderingsdetaljer from '../beredskapsperiode-vurderingsdetaljer/BeredskapsperiodeVurderingsdetaljer'; +import VurderingAvBeredskapsperioderForm from '../vurdering-av-beredskapsperioder-form/VurderingAvBeredskapsperioderForm'; +import Beskrivelse from '../../../../types/Beskrivelse'; +import Vurderingsresultat from '../../../../types/Vurderingsresultat'; + +interface BeredskapsperiodeoversiktControllerProps { + valgtPeriode: Vurderingsperiode; + editMode: boolean; + onEditClick: () => void; + onCancelClick: () => void; + beskrivelser: Beskrivelse[]; +} + +const BeredskapsperiodeoversiktController = ({ + valgtPeriode, + editMode, + onEditClick, + onCancelClick, + beskrivelser, +}: BeredskapsperiodeoversiktControllerProps) => { + if (valgtPeriode.resultat !== Vurderingsresultat.IKKE_VURDERT && !editMode) { + return ( + + ); + } + return ( + + ); +}; + +export default BeredskapsperiodeoversiktController; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiodeoversikt-messages/BeredskapsperiodeoversiktMessages.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiodeoversikt-messages/BeredskapsperiodeoversiktMessages.tsx new file mode 100644 index 0000000000..18ddba2c61 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperiodeoversikt-messages/BeredskapsperiodeoversiktMessages.tsx @@ -0,0 +1,28 @@ +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import BeredskapType from '../../../../types/BeredskapType'; +import { getStringMedPerioder } from '../../../../util/periodUtils'; +import CustomAlertstripe from '../../alertstripe/Alertstripe'; + +interface BeredskapsperiodeoversiktMessagesProps { + beredskapData: BeredskapType; +} + +const BeredskapsperiodeoversiktMessages = ({ beredskapData }: BeredskapsperiodeoversiktMessagesProps) => { + if (!beredskapData.harPerioder()) { + return

Søker har ikke oppgitt at det er behov for beredskap.

; + } + if (beredskapData.harPerioderTilVurdering()) { + const perioderTilVurdering = beredskapData.finnPerioderTilVurdering().map(({ periode }) => periode); + return ( + + + {`Vurder behov for beredskap i ${getStringMedPerioder(perioderTilVurdering)}.`} + + + ); + } + return null; +}; + +export default BeredskapsperiodeoversiktMessages; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperioderoversikt/Beredskapsperiodeoversikt.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperioderoversikt/Beredskapsperiodeoversikt.tsx new file mode 100644 index 0000000000..8d1dc673cf --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/beredskapsperioderoversikt/Beredskapsperiodeoversikt.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { useEffect } from 'react'; +import { NavigationWithDetailView } from '@navikt/ft-plattform-komponenter'; +import BeredskapType from '../../../../types/BeredskapType'; +import Vurderingsperiode from '../../../../types/Vurderingsperiode'; +import Periodenavigasjon from '../../periodenavigasjon/Periodenavigasjon'; +import BeredskapsperiodeoversiktController from '../beredskapsperiodeoversikt-controller/BeredskapsperiodeoversiktController'; +import BeredskapsperiodeoversiktMessages from '../beredskapsperiodeoversikt-messages/BeredskapsperiodeoversiktMessages'; + +interface BeredskapsperiodeoversiktProps { + beredskapData: BeredskapType; +} + +const Beredskapsperiodeoversikt = ({ beredskapData }: BeredskapsperiodeoversiktProps) => { + const [valgtPeriode, setValgtPeriode] = React.useState(null); + const [editMode, setEditMode] = React.useState(false); + const { beskrivelser } = beredskapData; + + const perioderTilVurdering = beredskapData.finnPerioderTilVurdering(); + const vurderteBeredskapsperioder = beredskapData.finnVurdertePerioder(); + + const velgPeriode = (periode: Vurderingsperiode) => { + setValgtPeriode(periode); + setEditMode(false); + }; + + useEffect(() => { + if (beredskapData.harPerioderTilVurdering()) { + setValgtPeriode(perioderTilVurdering[0]); + } + }, []); + + return ( + <> + + ( + + )} + showDetailSection={!!valgtPeriode} + detailSection={() => ( + setEditMode(true)} + onCancelClick={() => velgPeriode(null)} + beskrivelser={beskrivelser} + /> + )} + /> + + ); +}; + +export default Beredskapsperiodeoversikt; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/vurdering-av-beredskapsperioder-form/VurderingAvBeredskapsperioderForm.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/vurdering-av-beredskapsperioder-form/VurderingAvBeredskapsperioderForm.tsx new file mode 100644 index 0000000000..0f36f21585 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/beredskap/vurdering-av-beredskapsperioder-form/VurderingAvBeredskapsperioderForm.tsx @@ -0,0 +1,218 @@ +import { Period } from '@fpsak-frontend/utils'; +import { Box, Margin, DetailView, LabelledContent, Form } from '@navikt/ft-plattform-komponenter'; +import { PeriodpickerListRHF, RadioGroupPanelRHF, TextAreaRHF } from '@fpsak-frontend/form'; +import { AlertStripeInfo } from 'nav-frontend-alertstriper'; +import React from 'react'; +import { FormProvider, useForm, useWatch } from 'react-hook-form'; +import Beskrivelse from '../../../../types/Beskrivelse'; +import Vurderingsperiode from '../../../../types/Vurderingsperiode'; +import Vurderingsresultat from '../../../../types/Vurderingsresultat'; +import { finnResterendePerioder } from '../../../../util/periodUtils'; +import ContainerContext from '../../../context/ContainerContext'; +import { required } from '../../../form/validators/index'; +import AddButton from '../../add-button/AddButton'; +import BeskrivelserForPerioden from '../../beskrivelser-for-perioden/BeskrivelserForPerioden'; +import DeleteButton from '../../delete-button/DeleteButton'; + +export enum FieldName { + BEGRUNNELSE = 'begrunnelse', + HAR_BEHOV_FOR_BEREDSKAP = 'harBehovForBeredskap', + PERIODER = 'perioder', +} + +enum RadioOptions { + JA = 'ja', + JA_DELER = 'jaDeler', + NEI = 'nei', +} + +interface VurderingAvBeredskapsperioderFormProps { + beredskapsperiode: Vurderingsperiode; + onCancelClick: () => void; + beskrivelser: Beskrivelse[]; +} + +interface VurderingAvBeredskapsperioderFormState { + [FieldName.BEGRUNNELSE]: string; + [FieldName.PERIODER]: Period[]; + [FieldName.HAR_BEHOV_FOR_BEREDSKAP]: RadioOptions; +} + +const VurderingAvBeredskapsperioderForm = ({ + beredskapsperiode, + onCancelClick, + beskrivelser, +}: VurderingAvBeredskapsperioderFormProps): JSX.Element => { + const { lagreBeredskapvurdering, readOnly } = React.useContext(ContainerContext); + const [isSubmitting, setIsSubmitting] = React.useState(false); + const defaultBehovForBeredeskap = () => { + if (beredskapsperiode.resultat === Vurderingsresultat.OPPFYLT) { + return RadioOptions.JA; + } + if (beredskapsperiode.resultat === Vurderingsresultat.IKKE_OPPFYLT) { + return RadioOptions.NEI; + } + return null; + }; + + const formMethods = useForm({ + defaultValues: { + [FieldName.PERIODER]: beredskapsperiode?.periode + ? [new Period(beredskapsperiode.periode.fom, beredskapsperiode.periode.tom)] + : [new Period('', '')], + [FieldName.BEGRUNNELSE]: beredskapsperiode.begrunnelse || '', + [FieldName.HAR_BEHOV_FOR_BEREDSKAP]: defaultBehovForBeredeskap(), + }, + }); + + const erDetBehovForBeredskap = useWatch({ control: formMethods.control, name: FieldName.HAR_BEHOV_FOR_BEREDSKAP }); + + const handleSubmit = (formState: VurderingAvBeredskapsperioderFormState) => { + setIsSubmitting(true); + setTimeout(() => setIsSubmitting(false), 2500); + const { begrunnelse, perioder, harBehovForBeredskap } = formState; + const { kilde } = beredskapsperiode; + + let perioderMedEllerUtenBeredskap; + let perioderUtenBeredskap = []; + if (harBehovForBeredskap === RadioOptions.JA_DELER) { + perioderMedEllerUtenBeredskap = perioder + .map((periode: any) => (periode.period ? periode.period : periode)) + .map(periode => ({ + periode, + resultat: Vurderingsresultat.OPPFYLT, + begrunnelse, + kilde, + })); + + const resterendePerioder = finnResterendePerioder(perioder, beredskapsperiode.periode); + perioderUtenBeredskap = resterendePerioder.map(periode => ({ + periode, + resultat: Vurderingsresultat.IKKE_OPPFYLT, + begrunnelse, + kilde, + })); + } else { + perioderMedEllerUtenBeredskap = [ + { + periode: beredskapsperiode.periode, + resultat: + harBehovForBeredskap === RadioOptions.JA ? Vurderingsresultat.OPPFYLT : Vurderingsresultat.IKKE_OPPFYLT, + begrunnelse, + kilde, + }, + ]; + } + + const kombinertePerioder = perioderMedEllerUtenBeredskap.concat(perioderUtenBeredskap); + lagreBeredskapvurdering({ vurderinger: kombinertePerioder }); + }; + + const valgtePerioder = useWatch({ control: formMethods.control, name: FieldName.PERIODER }); + + const perioderUtenBehovForBeredskap = finnResterendePerioder( + (valgtePerioder || []) as any, + beredskapsperiode.periode, + ); + + return ( + + +
+ + + + + + + + + + {erDetBehovForBeredskap === RadioOptions.JA_DELER && ( + + + numberOfItems > 1 && ( + { + fieldArrayMethods.remove(index); + }} + /> + ) + } + renderAfterFieldArray={fieldArrayMethods => ( + + fieldArrayMethods.append({ fom: '', tom: '' })} + id="leggTilPeriodeKnapp" + /> + + )} + validators={{ + overlaps: (valgtPeriode: Period) => { + const andreValgtePerioder = formMethods + .getValues() + .perioder.filter((periodWrapper: any) => periodWrapper.period !== valgtPeriode) + .map(({ period }: any) => new Period(period.fom, period.tom)); + + // eslint-disable-next-line react/destructuring-assignment + const valgtPeriodePeriod = new Period(valgtPeriode.fom, valgtPeriode.tom); + if (valgtPeriodePeriod.overlapsWithSomePeriodInList(andreValgtePerioder)) { + return 'Beredskapsperiodene kan ikke overlappe'; + } + return null; + }, + }} + /> + + )} + {perioderUtenBehovForBeredskap.length > 0 && ( + + + ( +

+ {periode.prettifyPeriod()} +

+ ))} + /> +
+
+ )} +
+
+
+ ); +}; + +export default VurderingAvBeredskapsperioderForm; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/beskrivelser-for-perioden/BeskrivelserForPerioden.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/beskrivelser-for-perioden/BeskrivelserForPerioden.tsx new file mode 100644 index 0000000000..60029cf267 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/beskrivelser-for-perioden/BeskrivelserForPerioden.tsx @@ -0,0 +1,58 @@ +import { + Box, + Margin, + ContentWithTooltip, + LabelledContent, + OnePersonIconGray, + OnePersonOutlineGray, +} from '@navikt/ft-plattform-komponenter'; +import * as React from 'react'; +import Beskrivelse from '../../../types/Beskrivelse'; +import Kilde from '../../../types/Kilde'; +import { prettifyDate } from '../../../util/formats'; +import styles from './beskrivelserForPerioden.css'; + +interface BeskrivelserForPeriodenProps { + periodebeskrivelser: Beskrivelse[]; +} + +const getLabel = (periodebeskrivelse: Beskrivelse) => { + const kilde = periodebeskrivelse.kilde === Kilde.ANDRE ? 'annen part' : 'søker'; + return ( +
+ {periodebeskrivelse.kilde === Kilde.ANDRE ? ( + + + + ) : ( + + + + )} +

+ {`Beskrivelse fra ${kilde} + for perioden ${periodebeskrivelse.periode.prettifyPeriod()} (mottatt ${prettifyDate( + periodebeskrivelse.mottattDato, + )}):`} +

+
+ ); +}; + +const BeskrivelserForPerioden = ({ periodebeskrivelser }: BeskrivelserForPeriodenProps): JSX.Element | null => { + if (periodebeskrivelser?.length > 0) { + return ( + <> + {periodebeskrivelser.map((periodebeskrivelse, index) => ( + + + + ))} +
+ + ); + } + return null; +}; + +export default BeskrivelserForPerioden; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/beskrivelser-for-perioden/beskrivelserForPerioden.css b/packages/fakta-etablert-tilsyn/src/ui/components/beskrivelser-for-perioden/beskrivelserForPerioden.css new file mode 100644 index 0000000000..3ab0c60396 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/beskrivelser-for-perioden/beskrivelserForPerioden.css @@ -0,0 +1,15 @@ +.beskrivelserForPerioden__label { + display: flex; + align-items: flex-end; + margin-bottom: 0.75rem; +} + +.beskrivelserForPerioden__labelText { + margin: 0 0 0 0.75rem; +} + +.beskrivelserForPerioden__separator { + border: none; + border-bottom: 1px solid #c6c2bf; + margin: 1.75rem 0 0; +} diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/delete-button/DeleteButton.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/delete-button/DeleteButton.tsx new file mode 100644 index 0000000000..8b09613184 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/delete-button/DeleteButton.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { BucketIcon } from '@navikt/ft-plattform-komponenter'; +import styles from './deleteButton.css'; + +const DeleteButton = ({ onClick }) => ( +
+ +
+); + +export default DeleteButton; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/delete-button/deleteButton.css b/packages/fakta-etablert-tilsyn/src/ui/components/delete-button/deleteButton.css new file mode 100644 index 0000000000..201484ca4e --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/delete-button/deleteButton.css @@ -0,0 +1,13 @@ +.deleteButton__container { + margin-left: 1rem; + margin-bottom: 0.5rem; + align-self: flex-end; +} + +.deleteButton__button { + padding: 0; + border: none; + background: none; + height: 23px; + cursor: pointer; +} diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/EtablertTilsynDag.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/EtablertTilsynDag.tsx new file mode 100644 index 0000000000..b29c988b64 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/EtablertTilsynDag.tsx @@ -0,0 +1,29 @@ +import { Detail, Tag } from '@navikt/ds-react'; +import classNames from 'classnames'; +import React from 'react'; +import Kilde from '../../../types/Kilde'; + +import styles from './etablertTilsynRowContent.css'; +import PartIkon from './PartIkon'; + +interface OwnProps { + tittel: string; + timer: number; + kilde: Kilde; + disabled: boolean; + visIkon?: boolean; +} +const EtablertTilsynDag = ({ tittel, timer, kilde, disabled, visIkon = true }: OwnProps) => ( +
+ {tittel} + + {visIkon && } + {timer} + +
+); + +export default EtablertTilsynDag; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/EtablertTilsynMedSmoring.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/EtablertTilsynMedSmoring.tsx new file mode 100644 index 0000000000..ef51573e91 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/EtablertTilsynMedSmoring.tsx @@ -0,0 +1,178 @@ +import { isDayAfter, Period } from '@fpsak-frontend/utils'; +import { Table } from '@navikt/ds-react'; +import dayjs from 'dayjs'; +import { uniq } from 'lodash'; +import React from 'react'; +import EtablertTilsynType from '../../../types/EtablertTilsynType'; +import EtablertTilsynRowContent from './EtablertTilsynRowContent'; +import PartIkon from './PartIkon'; +import styles from './etablertTilsynMedSmoring.css'; + +interface EtablertTilsynProps { + etablertTilsynData: EtablertTilsynType[]; + smurtEtablertTilsynPerioder: EtablertTilsynType[]; + sykdomsperioderSomIkkeErOppfylt: Period[]; + perioderSomOverstyrerTilsyn: Period[]; +} + +interface EtablertTilsynMappet { + etablertTilsyn: EtablertTilsynType[]; + etablertTilsynSmurt: EtablertTilsynType[]; + uke: number; + delAvUke?: number; +} + +const erHelg = (dag: Date) => [6, 0].includes(dayjs(dag).day()); + +const ukeVisning = (uke, delAvUke) => { + if (delAvUke) { + return `Uke ${uke} - ${String.fromCharCode(64 + delAvUke)}`; + } + return `Uke ${uke}`; +}; + +const periodeVisning = (usmurtePerioder, smurtePerioder) => { + if (smurtePerioder.length) { + return new Period( + smurtePerioder[0].periode.fom, + smurtePerioder[smurtePerioder.length - 1].periode.tom, + ).prettifyPeriod(); + } + return new Period( + usmurtePerioder[0].periode.fom, + usmurtePerioder[usmurtePerioder.length - 1].periode.tom, + ).prettifyPeriod(); +}; + +const EtablertTilsyn = ({ + etablertTilsynData, + smurtEtablertTilsynPerioder, + sykdomsperioderSomIkkeErOppfylt, + perioderSomOverstyrerTilsyn, +}: EtablertTilsynProps): JSX.Element => { + const harVurderinger = etablertTilsynData.length > 0; + const avslaatteDager = sykdomsperioderSomIkkeErOppfylt.flatMap(periode => + periode.asListOfDays().map(date => new Period(date, date)), + ); + const dagerSomOverstyrerTilsyn = perioderSomOverstyrerTilsyn.flatMap(periode => + periode.asListOfDays().map(date => new Period(date, date)), + ); + + const avslaatteDagerFiltrert = avslaatteDager.filter( + v => !dagerSomOverstyrerTilsyn.some(innlagtDag => v.includesDate(innlagtDag.fom)), + ); + + const dagerSomSkalEkskluderes = [...avslaatteDagerFiltrert]; + const etablertTilsynEnkeltdager = etablertTilsynData.flatMap(v => + v.periode + .asListOfDays() + .filter(date => !erHelg(date)) + .map(date => ({ ...v, periode: new Period(date, date) })), + ); + const smurtEtablertTilsynEnkeltdager = smurtEtablertTilsynPerioder.flatMap(v => + v.periode + .asListOfDays() + .filter(date => !erHelg(date)) + .map(date => ({ ...v, periode: new Period(date, date) })), + ); + + const uker = uniq(etablertTilsynEnkeltdager.map(data => dayjs(data.periode.fom).week())); + const tilsynPerUke = uker.map(uke => ({ + etablertTilsyn: etablertTilsynEnkeltdager.filter(v => dayjs(v.periode.fom).week() === uke), + etablertTilsynSmurt: smurtEtablertTilsynEnkeltdager.filter(v => dayjs(v.periode.fom).week() === uke), + uke, + })); + const tilsynPerUkeOppdeltSmoering = []; + const tilsynPerUkeUtenOppdeltSmoering = tilsynPerUke + .map(v => ({ + ...v, + etablertTilsynSmurt: v.etablertTilsynSmurt.filter( + smurt => !dagerSomSkalEkskluderes.some(periode => periode.includesDate(smurt.periode.fom)), + ), + })) + .map(v => { + const smurtePerioder = [] as EtablertTilsynType[][]; + v.etablertTilsynSmurt.forEach(smurtPeriode => { + const sammenhengendePeriode = smurtePerioder.find(periodeArray => + periodeArray.find( + periode => + periode.tidPerDag === smurtPeriode.tidPerDag && + isDayAfter(dayjs(periode.periode.tom), dayjs(smurtPeriode.periode.fom)), + ), + ); + if (sammenhengendePeriode) { + sammenhengendePeriode.push(smurtPeriode); + } else { + smurtePerioder.push([smurtPeriode]); + } + }); + + smurtePerioder.forEach((smurtPeriode, index, array) => + tilsynPerUkeOppdeltSmoering.push({ + etablertTilsyn: v.etablertTilsyn, + etablertTilsynSmurt: smurtPeriode, + uke: v.uke, + delAvUke: array.length > 1 ? index + 1 : undefined, + }), + ); + return smurtePerioder.length ? null : v; + }) + .filter(Boolean); + const etablertTilsynMappet = [...tilsynPerUkeUtenOppdeltSmoering, ...tilsynPerUkeOppdeltSmoering].sort( + (a: EtablertTilsynMappet, b: EtablertTilsynMappet) => + new Date(a.etablertTilsynSmurt[0]?.periode?.fom).getTime() - + new Date(b.etablertTilsynSmurt[0]?.periode?.fom).getTime(), + ); + + if (!harVurderinger) { + return

Søker har ikke oppgitt etablert tilsyn

; + } + + return ( +
+ + + + + Uke + Periode + % tilsyn i periode + Part + + + + {etablertTilsynMappet.map(tilsyn => { + const tidPerDagArray = tilsyn.etablertTilsynSmurt?.map(v => v.tidPerDag).filter(Boolean); + const tidPerDag = tidPerDagArray[0] || 0; + const tilsynIPeriodeProsent = Math.round((tidPerDag / 7.5) * 100); + const parter = tilsyn.etablertTilsyn.map(v => v.kilde); + return ( + + } + > + {ukeVisning(tilsyn.uke, tilsyn.delAvUke)} + {periodeVisning(tilsyn.etablertTilsyn, tilsyn.etablertTilsynSmurt)} + {`${tilsynIPeriodeProsent}%`} + + Kilde + + + + ); + })} + +
+
+ ); +}; + +export default EtablertTilsyn; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/EtablertTilsynRowContent.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/EtablertTilsynRowContent.tsx new file mode 100644 index 0000000000..96fdeaa4a0 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/EtablertTilsynRowContent.tsx @@ -0,0 +1,130 @@ +import { BodyShort, Detail, HelpText, Label } from '@navikt/ds-react'; +import { Period } from '@fpsak-frontend/utils'; +import dayjs from 'dayjs'; +import React from 'react'; +import EtablertTilsynType from '../../../types/EtablertTilsynType'; +import PartIkon from './PartIkon'; +import styles from './etablertTilsynRowContent.css'; +import EtablertTilsynDag from './EtablertTilsynDag'; +import Kilde from '../../../types/Kilde'; + +interface TilsynMappet { + date: string; + tidPerDag: number; +} +interface OwnProps { + etablertTilsyn: EtablertTilsynType[]; + etablertTilsynSmurt: EtablertTilsynType[]; + dagerSomOverstyrerTilsyn: Period[]; + tilsynProsent: number; + visIkon: boolean; +} + +const EtablertTilsynRowContent = ({ + etablertTilsyn, + etablertTilsynSmurt, + dagerSomOverstyrerTilsyn, + tilsynProsent, + visIkon, +}: OwnProps) => { + const etablertTilsynDager = etablertTilsyn.flatMap(v => + v.periode.asListOfDays().map(date => ({ date, tidPerDag: v.tidPerDag, kilde: v.kilde })), + ); + + const etablertTilsynSmurtDager = etablertTilsynSmurt.flatMap(v => + v.periode.asListOfDays().map(date => ({ date, tidPerDag: v.tidPerDag })), + ); + + const mandag = etablertTilsynDager.find(v => dayjs(v.date).day() === 1); + const tirsdag = etablertTilsynDager.find(v => dayjs(v.date).day() === 2); + const onsdag = etablertTilsynDager.find(v => dayjs(v.date).day() === 3); + const torsdag = etablertTilsynDager.find(v => dayjs(v.date).day() === 4); + const fredag = etablertTilsynDager.find(v => dayjs(v.date).day() === 5); + + const mandagSmurt = etablertTilsynSmurtDager.find(v => dayjs(v.date).day() === 1); + const tirsdagSmurt = etablertTilsynSmurtDager.find(v => dayjs(v.date).day() === 2); + const onsdagSmurt = etablertTilsynSmurtDager.find(v => dayjs(v.date).day() === 3); + const torsdagSmurt = etablertTilsynSmurtDager.find(v => dayjs(v.date).day() === 4); + const fredagSmurt = etablertTilsynSmurtDager.find(v => dayjs(v.date).day() === 5); + + const dagOverstyres = (tilsyn: TilsynMappet) => dagerSomOverstyrerTilsyn.some(dag => dag.fom === tilsyn?.date); + + const timerSmurt = etablertTilsynSmurtDager.find(v => v.tidPerDag)?.tidPerDag; + + const skalDisables = (tilsynSmurt: TilsynMappet) => { + if (tilsynSmurt) { + return false; + } + + if (dagOverstyres(tilsynSmurt)) { + return true; + } + return true; + }; + return ( + <> +
+ + + Timer tilsyn innrapportert fordeles/smøres utover sammenhengende dager hvor behovet for tilsyn og pleie er + godkjent innenfor en uke. Søker graderes ikke mot timer tilsyn innrapportert på dager barnet er innlagt eller + dager foreldrene er i beredskap eller har nattevåk. + +
+
+ + + + + +
+ {`= ${timerSmurt || 0} t per dag (${tilsynProsent}%)`} +
+
+ {visIkon && ( +
+
+ + = søker +
+
+ + = annen part +
+
+ )} + + ); +}; + +export default EtablertTilsynRowContent; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/PartIkon.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/PartIkon.tsx new file mode 100644 index 0000000000..d4ffaac9c9 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/PartIkon.tsx @@ -0,0 +1,31 @@ +import { ContentWithTooltip } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import { CoApplicantFilled, PeopleFilled, People } from '@navikt/ds-icons'; +import Kilde from '../../../types/Kilde'; + +const PartIkon = ({ parter, fontSize = '24px' }: { parter: Kilde[]; fontSize?: string }) => { + if (parter.includes(Kilde.SØKER) && parter.includes(Kilde.ANDRE)) { + return ( + + + + ); + } + if (parter.includes(Kilde.SØKER)) { + return ( + + + + ); + } + if (parter.includes(Kilde.ANDRE)) { + return ( + + + + ); + } + return null; +}; + +export default PartIkon; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/etablertTilsynMedSmoring.css b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/etablertTilsynMedSmoring.css new file mode 100644 index 0000000000..0a8cd5460d --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/etablertTilsynMedSmoring.css @@ -0,0 +1,14 @@ +.etablertTilsynMedSmoringTabell { + max-width: 800px; +} + +.etablertTilsynMedSmoringTabell .visuallyHidden { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/etablertTilsynRowContent.css b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/etablertTilsynRowContent.css new file mode 100644 index 0000000000..59dc48bd5d --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/etablertTilsyn/etablertTilsynRowContent.css @@ -0,0 +1,43 @@ +.etablertTilsynRowContent { + display: grid; + margin-top: 0.8125rem; + grid-template-columns: repeat(5, 1fr) 2fr; +} + +.etablertTilsyn__tag { + margin-top: 0.25rem; + padding: 0.5rem; + min-width: 4.5625rem; + min-height: 2.5rem; + background-color: #e6f0ff; + border: none; +} + +.etablertTilsyn__tag__disabled { + background-image: repeating-linear-gradient(-45deg, #f1f1f1, #f1f1f1 3.33%, #cfcfcf 6.66%); +} + +.etablertTilsyn__timer__container { + display: flex; + align-items: flex-end; +} + +.etablertTilsyn__ikon__container { + margin-top: 1rem; + display: flex; + column-gap: 1rem; +} + +.etablertTilsyn__ikon__forklaring__container { + display: inline-flex; + align-items: center; +} + +.etablertTilsyn__innrapportert_timer__container { + display: flex; + column-gap: 0.625rem; +} + +.marginTop_1rem { + margin-top: 1rem; +} diff --git "a/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiode-vurderingsdetaljer/Nattev\303\245ksperiodeVurderingsdetaljer.tsx" "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiode-vurderingsdetaljer/Nattev\303\245ksperiodeVurderingsdetaljer.tsx" new file mode 100644 index 0000000000..00a10baaf8 --- /dev/null +++ "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiode-vurderingsdetaljer/Nattev\303\245ksperiodeVurderingsdetaljer.tsx" @@ -0,0 +1,61 @@ +import { Box, Margin, DetailView, LabelledContent, LinkButton, AssessedBy } from '@navikt/ft-plattform-komponenter'; +import React, { useContext } from 'react'; +import Beskrivelse from '../../../../types/Beskrivelse'; +import Vurderingsperiode from '../../../../types/Vurderingsperiode'; +import Vurderingsresultat from '../../../../types/Vurderingsresultat'; +import ContainerContext from '../../../context/ContainerContext'; +import BeskrivelserForPerioden from '../../beskrivelser-for-perioden/BeskrivelserForPerioden'; +import WriteAccessBoundContent from '../../write-access-bound-content/WriteAccessBoundContent'; +import styles from './nattevåksperiodeVurderingsdetaljer.css'; + +interface NattevåksperiodeVurderingsdetaljerProps { + nattevåksperiode: Vurderingsperiode; + onEditClick: () => void; + beskrivelser: Beskrivelse[]; +} + +const NattevåksperiodeVurderingsdetaljer = ({ + nattevåksperiode, + onEditClick, + beskrivelser, +}: NattevåksperiodeVurderingsdetaljerProps) => { + const { saksbehandlere } = useContext(ContainerContext); + const { opprettetAv, opprettetTidspunkt } = nattevåksperiode; + return ( + ( + ( + + Rediger vurdering + + )} + /> + )} + > + + + + + + + + + + + + + + + ); +}; + +export default NattevåksperiodeVurderingsdetaljer; diff --git "a/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiode-vurderingsdetaljer/nattev\303\245ksperiodeVurderingsdetaljer.css" "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiode-vurderingsdetaljer/nattev\303\245ksperiodeVurderingsdetaljer.css" new file mode 100644 index 0000000000..9411969476 --- /dev/null +++ "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiode-vurderingsdetaljer/nattev\303\245ksperiodeVurderingsdetaljer.css" @@ -0,0 +1,3 @@ +.endreLink { + margin-left: 1rem; +} diff --git "a/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiodeoversikt-controller/Nattev\303\245ksperiodeoversiktController.tsx" "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiodeoversikt-controller/Nattev\303\245ksperiodeoversiktController.tsx" new file mode 100644 index 0000000000..b07b702803 --- /dev/null +++ "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiodeoversikt-controller/Nattev\303\245ksperiodeoversiktController.tsx" @@ -0,0 +1,42 @@ +import * as React from 'react'; +import Vurderingsperiode from '../../../../types/Vurderingsperiode'; +import NattevåksperiodeVurderingsdetaljer from '../nattevåksperiode-vurderingsdetaljer/NattevåksperiodeVurderingsdetaljer'; +import VurderingAvNattevåksperioderForm from '../vurdering-av-nattevåksperioder-form/VurderingAvNattevåksperioderForm'; +import Beskrivelse from '../../../../types/Beskrivelse'; +import Vurderingsresultat from '../../../../types/Vurderingsresultat'; + +interface NattevåksperiodeoversiktControllerProps { + valgtPeriode: Vurderingsperiode; + editMode: boolean; + onEditClick: () => void; + onCancelClick: () => void; + beskrivelser: Beskrivelse[]; +} + +const NattevåksperiodeoversiktController = ({ + valgtPeriode, + editMode, + onEditClick, + onCancelClick, + beskrivelser, +}: NattevåksperiodeoversiktControllerProps) => { + if (valgtPeriode.resultat !== Vurderingsresultat.IKKE_VURDERT && !editMode) { + return ( + + ); + } + return ( + + ); +}; + +export default NattevåksperiodeoversiktController; diff --git "a/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiodeoversikt-messages/Nattev\303\245ksperiodeoversiktMessages.tsx" "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiodeoversikt-messages/Nattev\303\245ksperiodeoversiktMessages.tsx" new file mode 100644 index 0000000000..dd8d56c15c --- /dev/null +++ "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiodeoversikt-messages/Nattev\303\245ksperiodeoversiktMessages.tsx" @@ -0,0 +1,28 @@ +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import NattevåkType from '../../../../types/NattevåkType'; +import { getStringMedPerioder } from '../../../../util/periodUtils'; +import CustomAlertstripe from '../../alertstripe/Alertstripe'; + +interface NattevåksperiodeoversiktMessagesProps { + nattevåkData: NattevåkType; +} + +const NattevåksperiodeoversiktMessages = ({ nattevåkData }: NattevåksperiodeoversiktMessagesProps) => { + if (!nattevåkData.harPerioder()) { + return

Søker har ikke oppgitt at det er behov for nattevåk.

; + } + if (nattevåkData.harPerioderTilVurdering()) { + const perioderTilVurdering = nattevåkData.finnPerioderTilVurdering().map(({ periode }) => periode); + return ( + + + {`Vurder behov for nattevåk i ${getStringMedPerioder(perioderTilVurdering)}.`} + + + ); + } + return null; +}; + +export default NattevåksperiodeoversiktMessages; diff --git "a/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiodeoversikt/Nattev\303\245ksperiodeoversikt.tsx" "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiodeoversikt/Nattev\303\245ksperiodeoversikt.tsx" new file mode 100644 index 0000000000..5c11f1751e --- /dev/null +++ "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/nattev\303\245ksperiodeoversikt/Nattev\303\245ksperiodeoversikt.tsx" @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { useEffect } from 'react'; +import { NavigationWithDetailView } from '@navikt/ft-plattform-komponenter'; +import NattevåkType from '../../../../types/NattevåkType'; +import Vurderingsperiode from '../../../../types/Vurderingsperiode'; +import Periodenavigasjon from '../../periodenavigasjon/Periodenavigasjon'; +import NattevåksperiodeoversiktController from '../nattevåksperiodeoversikt-controller/NattevåksperiodeoversiktController'; +import NattevåksperiodeoversiktMessages from '../nattevåksperiodeoversikt-messages/NattevåksperiodeoversiktMessages'; + +interface NattevåksperiodeoversiktProps { + nattevåkData: NattevåkType; +} + +const Nattevåksperiodeoversikt = ({ nattevåkData }: NattevåksperiodeoversiktProps) => { + const [valgtPeriode, setValgtPeriode] = React.useState(null); + const [editMode, setEditMode] = React.useState(false); + const { beskrivelser } = nattevåkData; + + const perioderTilVurdering = nattevåkData.finnPerioderTilVurdering(); + const vurderteNattevåksperioder = nattevåkData.finnVurdertePerioder(); + + const velgPeriode = (periode: Vurderingsperiode) => { + setValgtPeriode(periode); + setEditMode(false); + }; + + useEffect(() => { + if (nattevåkData.harPerioderTilVurdering()) { + setValgtPeriode(perioderTilVurdering[0]); + } + }, []); + + return ( + <> + + ( + + )} + showDetailSection={!!valgtPeriode} + detailSection={() => ( + setEditMode(true)} + onCancelClick={() => velgPeriode(null)} + beskrivelser={beskrivelser} + /> + )} + /> + + ); +}; + +export default Nattevåksperiodeoversikt; diff --git "a/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/vurdering-av-nattev\303\245ksperioder-form/VurderingAvNattev\303\245ksperioderForm.tsx" "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/vurdering-av-nattev\303\245ksperioder-form/VurderingAvNattev\303\245ksperioderForm.tsx" new file mode 100644 index 0000000000..d3efb42d7c --- /dev/null +++ "b/packages/fakta-etablert-tilsyn/src/ui/components/nattev\303\245k/vurdering-av-nattev\303\245ksperioder-form/VurderingAvNattev\303\245ksperioderForm.tsx" @@ -0,0 +1,196 @@ +import { Period } from '@fpsak-frontend/utils'; +import { Box, Margin, DetailView, LabelledContent, Form } from '@navikt/ft-plattform-komponenter'; +import { PeriodpickerListRHF, RadioGroupPanelRHF, TextAreaRHF } from '@fpsak-frontend/form'; +import { AlertStripeInfo } from 'nav-frontend-alertstriper'; +import React from 'react'; +import { FormProvider, useForm, useWatch } from 'react-hook-form'; +import Beskrivelse from '../../../../types/Beskrivelse'; +import Vurderingsperiode from '../../../../types/Vurderingsperiode'; +import Vurderingsresultat from '../../../../types/Vurderingsresultat'; +import { finnResterendePerioder } from '../../../../util/periodUtils'; +import ContainerContext from '../../../context/ContainerContext'; + +import AddButton from '../../add-button/AddButton'; +import BeskrivelserForPerioden from '../../beskrivelser-for-perioden/BeskrivelserForPerioden'; +import DeleteButton from '../../delete-button/DeleteButton'; + +export enum FieldName { + BEGRUNNELSE = 'begrunnelse', + HAR_BEHOV_FOR_NATTEVÅK = 'harBehovForNattevåk', + PERIODER = 'perioder', +} + +enum RadioOptions { + JA = 'ja', + JA_DELER = 'jaDeler', + NEI = 'nei', +} + +interface VurderingAvNattevåksperioderFormProps { + nattevåksperiode: Vurderingsperiode; + onCancelClick: () => void; + beskrivelser: Beskrivelse[]; +} + +interface VurderingAvNattevåksperioderFormState { + [FieldName.BEGRUNNELSE]: string; + [FieldName.PERIODER]: Period[]; + [FieldName.HAR_BEHOV_FOR_NATTEVÅK]: RadioOptions; +} + +const VurderingAvNattevåksperioderForm = ({ + nattevåksperiode, + onCancelClick, + beskrivelser, +}: VurderingAvNattevåksperioderFormProps): JSX.Element => { + const { lagreNattevåkvurdering, readOnly } = React.useContext(ContainerContext); + const [isSubmitting, setIsSubmitting] = React.useState(false); + const defaultBehovForNattevåk = () => { + if (nattevåksperiode.resultat === Vurderingsresultat.OPPFYLT) { + return RadioOptions.JA; + } + if (nattevåksperiode.resultat === Vurderingsresultat.IKKE_OPPFYLT) { + return RadioOptions.NEI; + } + return null; + }; + const formMethods = useForm({ + defaultValues: { + [FieldName.PERIODER]: nattevåksperiode?.periode + ? [new Period(nattevåksperiode.periode.fom, nattevåksperiode.periode.tom)] + : [new Period('', '')], + [FieldName.BEGRUNNELSE]: nattevåksperiode.begrunnelse || '', + [FieldName.HAR_BEHOV_FOR_NATTEVÅK]: defaultBehovForNattevåk(), + }, + }); + + const erDetBehovForNattevåk = useWatch({ control: formMethods.control, name: FieldName.HAR_BEHOV_FOR_NATTEVÅK }); + + const handleSubmit = (formState: VurderingAvNattevåksperioderFormState) => { + setIsSubmitting(true); + setTimeout(() => setIsSubmitting(false), 2500); + const { begrunnelse, perioder, harBehovForNattevåk } = formState; + const { kilde } = nattevåksperiode; + + let perioderMedEllerUtenNattevåk; + let perioderUtenNattevåk = []; + if (harBehovForNattevåk === RadioOptions.JA_DELER) { + perioderMedEllerUtenNattevåk = perioder + .map((periode: any) => (periode.period ? periode.period : periode)) + .map(periode => ({ + periode, + resultat: Vurderingsresultat.OPPFYLT, + begrunnelse, + kilde, + })); + + const resterendePerioder = finnResterendePerioder(perioder, nattevåksperiode.periode); + perioderUtenNattevåk = resterendePerioder.map(periode => ({ + periode, + resultat: Vurderingsresultat.IKKE_OPPFYLT, + begrunnelse, + kilde, + })); + } else { + perioderMedEllerUtenNattevåk = [ + { + periode: nattevåksperiode.periode, + resultat: + harBehovForNattevåk === RadioOptions.JA ? Vurderingsresultat.OPPFYLT : Vurderingsresultat.IKKE_OPPFYLT, + begrunnelse, + kilde, + }, + ]; + } + + const kombinertePerioder = perioderMedEllerUtenNattevåk.concat(perioderUtenNattevåk); + lagreNattevåkvurdering({ vurderinger: kombinertePerioder }); + }; + + const valgtePerioder = useWatch({ control: formMethods.control, name: FieldName.PERIODER }); + const perioderUtenBehovForNattevåk = finnResterendePerioder((valgtePerioder || []) as any, nattevåksperiode.periode); + + return ( + + +
+ + + + + + + + + + {erDetBehovForNattevåk === RadioOptions.JA_DELER && ( + + + numberOfItems > 1 && ( + { + fieldArrayMethods.remove(index); + }} + /> + ) + } + renderAfterFieldArray={fieldArrayMethods => ( + + fieldArrayMethods.append({ fom: '', tom: '' })} + id="leggTilPeriodeKnapp" + /> + + )} + /> + + )} + {perioderUtenBehovForNattevåk.length > 0 && ( + + + ( +

+ {periode.prettifyPeriod()} +

+ ))} + /> +
+
+ )} +
+
+
+ ); +}; + +export default VurderingAvNattevåksperioderForm; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/periode-som-skal-vurderes/PeriodeSomSkalVurderes.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/periode-som-skal-vurderes/PeriodeSomSkalVurderes.tsx new file mode 100644 index 0000000000..3ad1b08a8b --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/periode-som-skal-vurderes/PeriodeSomSkalVurderes.tsx @@ -0,0 +1,32 @@ +import { Period } from '@fpsak-frontend/utils'; +import React from 'react'; +import { ContentWithTooltip, OnePersonIconGray, WarningIcon } from '@navikt/ft-plattform-komponenter'; +import styles from './periodeSomSkalVurderes.css'; + +interface PeriodeSomSkalVurderesProps { + periode: Period; +} + +const PeriodeSomSkalVurderes = ({ periode }: PeriodeSomSkalVurderesProps) => ( +
+ Type + + + +
+
+

+ {periode.prettifyPeriod()} +

+
+
+ Kilde + + + +
+
+
+); + +export default PeriodeSomSkalVurderes; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/periode-som-skal-vurderes/periodeSomSkalVurderes.css b/packages/fakta-etablert-tilsyn/src/ui/components/periode-som-skal-vurderes/periodeSomSkalVurderes.css new file mode 100644 index 0000000000..7c2d988d7b --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/periode-som-skal-vurderes/periodeSomSkalVurderes.css @@ -0,0 +1,39 @@ +.periodeSomSkalVurderes { + display: flex; + align-items: center; + cursor: pointer; +} + +.periodeSomSkalVurderes__texts { + margin-left: 1.625rem; + display: flex; + align-items: center; +} + +.periodeSomSkalVurderes__texts__period { + margin: 0; + color: #0067c5; + text-decoration: underline; + min-width: 10.125rem; +} + +.periodeSomSkalVurderes__texts__kildeIcon { + display: inline-block; + vertical-align: top; + margin-left: 2.5rem; +} + +.periodeSomSkalVurderes__etikett { + margin-left: 1.375rem; +} + +.periodeSomSkalVurderes .visuallyHidden { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/periodenavigasjon/Periodenavigasjon.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/periodenavigasjon/Periodenavigasjon.tsx new file mode 100644 index 0000000000..9e3d6d5ab9 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/periodenavigasjon/Periodenavigasjon.tsx @@ -0,0 +1,77 @@ +import { Box, Margin, InteractiveList } from '@navikt/ft-plattform-komponenter'; +import { Element, Undertittel } from 'nav-frontend-typografi'; +import React, { useEffect } from 'react'; +import Vurderingsperiode from '../../../types/Vurderingsperiode'; +import { usePrevious } from '../../../util/hooks'; +import PeriodeSomSkalVurderes from '../periode-som-skal-vurderes/PeriodeSomSkalVurderes'; +import VurderingsperiodeElement from '../vurderingsperiode/VurderingsperiodeElement'; +import styles from './periodenavigasjon.css'; + +interface PeriodenavigasjonProps { + perioderTilVurdering: Vurderingsperiode[]; + vurdertePerioder: Vurderingsperiode[]; + onPeriodeValgt: (periode: Vurderingsperiode) => void; + harValgtPeriode?: boolean; +} + +const Periodenavigasjon = ({ + perioderTilVurdering, + vurdertePerioder, + onPeriodeValgt, + harValgtPeriode, +}: PeriodenavigasjonProps): JSX.Element => { + const harPerioderSomSkalVurderes = perioderTilVurdering?.length > 0; + const [activeIndex, setActiveIndex] = React.useState(harPerioderSomSkalVurderes ? 0 : -1); + const previousHarValgtPeriode = usePrevious(harValgtPeriode); + + useEffect(() => { + if (harValgtPeriode === false && previousHarValgtPeriode === true) { + setActiveIndex(-1); + } + }, [harValgtPeriode]); + + const vurdertePerioderElements = vurdertePerioder.map(({ periode, resultat, kilde }) => ( + + )); + + const periodeTilVurderingElements = perioderTilVurdering.map(({ periode }) => ( + + )); + + const perioder = [...perioderTilVurdering, ...vurdertePerioder]; + const elements = [...periodeTilVurderingElements, ...vurdertePerioderElements]; + const antallPerioder = elements.length; + + return ( +
+ + Alle perioder + + {antallPerioder === 0 &&

Ingen vurderinger å vise

} + {antallPerioder > 0 && ( +
+
+ Status + Periode + + Kilde +
+ ({ + content: element, + active: activeIndex === currentIndex, + key: `${currentIndex}`, + onClick: () => { + setActiveIndex(currentIndex); + const periodeIndex = elements.indexOf(element); + onPeriodeValgt(perioder[periodeIndex]); + }, + }))} + /> +
+ )} +
+ ); +}; + +export default Periodenavigasjon; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/periodenavigasjon/periodenavigasjon.css b/packages/fakta-etablert-tilsyn/src/ui/components/periodenavigasjon/periodenavigasjon.css new file mode 100644 index 0000000000..606ae0ec69 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/periodenavigasjon/periodenavigasjon.css @@ -0,0 +1,53 @@ +.nyVurderingKnapp { + flex-basis: 40%; + margin-left: auto; +} + +.vurderingsvelgerContainer { + flex-grow: 1; +} + +.vurderingsvelgerContainer__columnHeadings { + display: flex; + margin-bottom: 0.75rem; +} + +.vurderingsvelgerContainer__columnHeading--first { + margin-left: 0.875rem; +} + +.vurderingsvelgerContainer__columnHeading--second { + margin-left: 1.125rem; +} + +.vurderingsvelgerContainer__columnHeading--third { + margin-left: 8.875rem; +} + +.vurderingsperiode__postElementContainer { + margin-left: 1.375rem; + display: flex; +} + +.vurderingsperiode__postElementContainer :nth-child(2) { + margin-left: 1rem; +} + +.vurderingsnavigasjon { + position: relative; +} + +.vurderingsnavigasjon__opprettVurderingKnapp { + position: absolute; + right: 0; + top: 4px; +} + +.vurderingsnavigasjon__heading { + padding: 1.25rem 0.9375rem 0; +} + +.vurderingsnavigasjon__emptyText { + margin-left: 0.9375rem; + margin-right: 0.9375rem; +} diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/vurderingsperiode/VurderingsperiodeElement.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/vurderingsperiode/VurderingsperiodeElement.tsx new file mode 100644 index 0000000000..803ac93f8e --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/vurderingsperiode/VurderingsperiodeElement.tsx @@ -0,0 +1,78 @@ +import { Period } from '@fpsak-frontend/utils'; +import React from 'react'; +import { + ContentWithTooltip, + GreenCheckIconFilled, + OnePersonIconGray, + OnePersonOutlineGray, + RedCrossIconFilled, +} from '@navikt/ft-plattform-komponenter'; +import Kilde from '../../../types/Kilde'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import styles from './vurderingsperiodeElement.css'; + +interface VurderingsperiodeElementProps { + periode: Period; + resultat: Vurderingsresultat; + kilde: Kilde; + renderAfterElement?: () => React.ReactNode; +} + +const renderStatusIcon = (resultat: Vurderingsresultat) => { + if (resultat === Vurderingsresultat.OPPFYLT) { + return ( + + + + ); + } + if (resultat === Vurderingsresultat.IKKE_OPPFYLT) { + return ( + + + + ); + } + return null; +}; + +const renderKildeIcon = (kilde: Kilde) => { + if (kilde === Kilde.ANDRE) { + return ( + + + + ); + } + + return ( + + + + ); +}; + +const VurderingsperiodeElement = ({ + periode, + resultat, + kilde, + renderAfterElement, +}: VurderingsperiodeElementProps): JSX.Element => ( +
+ Type + {renderStatusIcon(resultat)} +
+

+ Periode + {periode.prettifyPeriod()} +

+
+
+ Kilde + {renderKildeIcon(kilde)} +
+ {renderAfterElement && renderAfterElement()} +
+); + +export default VurderingsperiodeElement; diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/vurderingsperiode/vurderingsperiodeElement.css b/packages/fakta-etablert-tilsyn/src/ui/components/vurderingsperiode/vurderingsperiodeElement.css new file mode 100644 index 0000000000..8995d0370b --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/vurderingsperiode/vurderingsperiodeElement.css @@ -0,0 +1,41 @@ +.vurderingsperiodeElement { + display: flex; + cursor: pointer; +} + +.vurderingsperiodeElement__texts { + display: flex; + align-items: center; + margin-left: 1.625rem; +} + +.vurderingsperiodeElement__texts__period { + display: inline-block; + margin: 0; + color: #0067c5; + text-decoration: underline; + min-width: 10.125rem; +} + +.vurderingsperiodeElement__texts__resultat { + display: inline-block; + margin: 0 0 0 1.375rem; +} + +.vurderingsperiodeElement__texts__kildeIcon { + display: inline-block; + vertical-align: top; + margin-top: -0.125rem; + margin-left: 2.5rem; +} + +.vurderingsperiodeElement .visuallyHidden { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} diff --git a/packages/fakta-etablert-tilsyn/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx b/packages/fakta-etablert-tilsyn/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx new file mode 100644 index 0000000000..05fe3c3eaf --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import ContainerContext from '../../context/ContainerContext'; + +interface WriteAccessBoundContentProps { + contentRenderer: () => JSX.Element; + otherRequirementsAreMet?: boolean; +} + +const WriteAccessBoundContent = ({ + contentRenderer, + otherRequirementsAreMet, +}: WriteAccessBoundContentProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + if (readOnly === false && (otherRequirementsAreMet === true || otherRequirementsAreMet === undefined)) { + return contentRenderer(); + } + return null; +}; + +export default WriteAccessBoundContent; diff --git a/packages/fakta-etablert-tilsyn/src/ui/context/ContainerContext.ts b/packages/fakta-etablert-tilsyn/src/ui/context/ContainerContext.ts new file mode 100644 index 0000000000..b4cd425e6a --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/context/ContainerContext.ts @@ -0,0 +1,5 @@ +import React from 'react'; +import ContainerContract from '../../types/ContainerContract'; + +const ContainerContext = React.createContext(null); +export default ContainerContext; diff --git a/packages/fakta-etablert-tilsyn/src/ui/form/validators/index.ts b/packages/fakta-etablert-tilsyn/src/ui/form/validators/index.ts new file mode 100644 index 0000000000..4b57123b06 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/form/validators/index.ts @@ -0,0 +1,73 @@ +import { Dayjs } from 'dayjs'; +import { Period } from '@fpsak-frontend/utils'; +import { dateFromString } from '../../../util/dateUtils'; +import { tomorrow } from '../../../constants/dateConstants'; + +export function required(v: any) { + if (v === null || v === undefined || v === '') { + return 'Du må oppgi en verdi'; + } + return true; +} + +export function dateIsNotInTheFuture(dateString: string): string | boolean { + const date: Dayjs = dateFromString(dateString); + if (date.isSame(tomorrow) || date.isAfter(tomorrow)) { + return 'Datoen kan ikke settes senere enn dagens dato'; + } + return true; +} + +export const detErTilsynsbehovPåDatoen = (dato: any, perioderMedTilsynsbehov: Period[]): string | boolean => { + const detErTilsynsbehovPåDato = perioderMedTilsynsbehov.some(periode => + new Period(periode.fom, periode.tom).includesDate(dato), + ); + if (detErTilsynsbehovPåDato) { + return true; + } + return 'Dato må være innenfor en periode med tilsynsbehov'; +}; + +export const datoenInngårISøknadsperioden = (dato: any, søknadsperiode: Period): string | boolean => { + if (søknadsperiode.includesDate(dato)) { + return true; + } + + return 'Dato må være innenfor søknadsperioden'; +}; + +export const detErIngenInnleggelsePåDato = (dato: any, innleggelsesperioder: Period[]): string | boolean => { + const detErInnleggelsePåDato = innleggelsesperioder.some(periode => + new Period(periode.fom, periode.tom).includesDate(dato), + ); + if (detErInnleggelsePåDato) { + return 'Dato må være utenfor innleggelsesperioden(e)'; + } + return true; +}; + +export const datoErInnenforResterendeVurderingsperioder = ( + dato: any, + resterendeVurderingsperioder: Period[], +): string | true => { + const datoErInnenfor = resterendeVurderingsperioder.some(period => + new Period(period.fom, period.tom).includesDate(dato), + ); + + if (datoErInnenfor) { + return true; + } + + return 'Dato må være innenfor periodene som vurderes'; +}; + +export const fomDatoErFørTomDato = (periode: Period): string | true => { + const fom = dateFromString(periode.fom); + const tom = dateFromString(periode.tom); + + if (fom.isAfter(tom)) { + return 'Fra-dato må være før til-dato'; + } + + return true; +}; diff --git a/packages/fakta-etablert-tilsyn/src/ui/mainActionTypes.ts b/packages/fakta-etablert-tilsyn/src/ui/mainActionTypes.ts new file mode 100644 index 0000000000..84554d8cc9 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/mainActionTypes.ts @@ -0,0 +1,10 @@ +enum MainActionType { + OK = 'OK', + FAILED = 'FAILED', + PENDING = 'PENDING', + SYKDOM_OK = 'SYKDOM_OK', + SYKDOM_FAILED = 'SYKDOM_FAILED', + SYKDOM_PENDING = 'SYKDOM_PENDING', +} + +export default MainActionType; diff --git a/packages/fakta-etablert-tilsyn/src/ui/mainComponent.css b/packages/fakta-etablert-tilsyn/src/ui/mainComponent.css new file mode 100644 index 0000000000..f7f6972a82 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/mainComponent.css @@ -0,0 +1,41 @@ +.mainComponent { + max-width: 75rem; + color: #262626; + margin-top: 1.25rem; +} + +.mainComponent__alertstripe :global .alertstripe__tekst { + max-width: 50rem; +} + +.mainComponent__contentContainer { + margin-top: 2rem; + margin-bottom: 8rem; +} + +.mainComponent :global .nav-frontend-tabs__tab-label { + padding-bottom: 0.1rem; +} + +.mainComponent :global .nav-frontend-tabs__tab-inner--aktiv .nav-frontend-tabs__tab-label { + border-bottom: 2px solid #0067c5; +} + +.mainComponent :global .alertstripe { + padding-top: 0.4375rem; + padding-bottom: 0.4375rem; +} + +.tabItem { + position: relative; +} + +.tabItem__warningIcon { + position: absolute; + right: 0; + bottom: -4px; +} + +.tabItemExtended { + padding-right: 2rem; +} diff --git a/packages/fakta-etablert-tilsyn/src/ui/mainReducer.ts b/packages/fakta-etablert-tilsyn/src/ui/mainReducer.ts new file mode 100644 index 0000000000..3ac8e57ec5 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/ui/mainReducer.ts @@ -0,0 +1,83 @@ +import { Period } from '@fpsak-frontend/utils'; +import BeredskapType from '../types/BeredskapType'; +import EtablertTilsynType from '../types/EtablertTilsynType'; +import NattevåkType from '../types/NattevåkType'; +import { SykdomResponse, TilsynResponse } from '../types/TilsynResponse'; +import ActionType from './mainActionTypes'; + +interface MainComponentState { + etablertTilsyn: EtablertTilsynType[]; + beredskap: BeredskapType; + nattevåk: NattevåkType; + smurtEtablertTilsynPerioder: EtablertTilsynType[]; + sykdomsperioderSomIkkeErOppfylt: Period[]; + tilsynHarFeilet: boolean; + sykdomHarFeilet: boolean; + isLoading: boolean; +} + +interface Action { + type: ActionType; + tilsynResponse?: TilsynResponse; + sykdomResponse?: SykdomResponse; +} + +const mainComponentReducer = (state: MainComponentState, action: Action): Partial => { + switch (action.type) { + case ActionType.OK: { + const { tilsynResponse } = action; + const etablertTilsyn = tilsynResponse.etablertTilsynPerioder.map( + etablertTilsynPeriode => new EtablertTilsynType(etablertTilsynPeriode), + ); + const beredskap = new BeredskapType(tilsynResponse.beredskap); + const nattevåk = new NattevåkType(tilsynResponse.nattevåk); + const smurtEtablertTilsynPerioder = tilsynResponse.smortEtablertTilsynPerioder.map( + etablertTilsynPeriode => new EtablertTilsynType(etablertTilsynPeriode), + ); + return { + ...state, + etablertTilsyn, + beredskap, + nattevåk, + smurtEtablertTilsynPerioder, + tilsynHarFeilet: false, + isLoading: false, + }; + } + case ActionType.FAILED: + return { + ...state, + tilsynHarFeilet: true, + isLoading: false, + }; + case ActionType.PENDING: + return { + ...state, + tilsynHarFeilet: false, + isLoading: true, + }; + case ActionType.SYKDOM_OK: { + const { sykdomResponse } = action; + const resterendeVurderingsperioder = sykdomResponse?.resterendeVurderingsperioder?.map( + v => new Period(v.fom, v.tom), + ); + const sykdomsperioderSomIkkeErOppfylt = sykdomResponse.vurderingselementer + .filter(v => v.resultat !== 'OPPFYLT') + .map(v => new Period(v.periode.fom, v.periode.tom)); + return { + ...state, + sykdomsperioderSomIkkeErOppfylt: [...sykdomsperioderSomIkkeErOppfylt, ...resterendeVurderingsperioder], + sykdomHarFeilet: false, + }; + } + case ActionType.SYKDOM_FAILED: + return { + ...state, + sykdomHarFeilet: true, + }; + default: + return { ...state }; + } +}; + +export default mainComponentReducer; diff --git a/packages/fakta-etablert-tilsyn/src/util/__tests__/formats.spec.ts b/packages/fakta-etablert-tilsyn/src/util/__tests__/formats.spec.ts new file mode 100644 index 0000000000..4bc8e4534a --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/util/__tests__/formats.spec.ts @@ -0,0 +1,9 @@ +import { prettifyDate } from '../formats'; + +test('prettifyDate', () => { + const expectedEqual = '10.09.2020'; + expect(prettifyDate('2020-09-10')).toBe(expectedEqual); + + const expectedNotEqual = '10.9.2020'; + expect(prettifyDate('2020-09-10')).not.toBe(expectedNotEqual); +}); diff --git a/packages/fakta-etablert-tilsyn/src/util/dateUtils.ts b/packages/fakta-etablert-tilsyn/src/util/dateUtils.ts new file mode 100644 index 0000000000..f57febd0af --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/util/dateUtils.ts @@ -0,0 +1,69 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import duration from 'dayjs/plugin/duration'; +import weekOfYear from 'dayjs/plugin/weekOfYear'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; +import { Period } from '@fpsak-frontend/utils'; + +const dateFormats = ['YYYY-MM-DD', 'DD.MM.YYYY']; + +dayjs.extend(utc); +dayjs.extend(duration); +dayjs.extend(customParseFormat); +dayjs.extend(weekOfYear); + +export function isSameOrBefore(date, otherDate) { + const dateInQuestion = dayjs(date, dateFormats).utc(true); + const formattedOtherDate = dayjs(otherDate, dateFormats).utc(true); + return dateInQuestion.isBefore(formattedOtherDate) || dateInQuestion.isSame(formattedOtherDate); +} + +export function dateFromString(dateString: string) { + return dayjs(dateString, dateFormats).utc(true); +} + +export function getPeriodAsListOfDays(period: Period) { + const fom = dayjs(period.fom).utc(true); + const tom = dayjs(period.tom).utc(true); + + const list = []; + for (let currentDate = fom; isSameOrBefore(currentDate, tom); currentDate = currentDate.add(1, 'day')) { + list.push(currentDate.format('YYYY-MM-DD')); + } + + return list; +} + +function getDaySequencesAsListOfPeriods(daySequences: string[][]): Period[] { + return daySequences.map(daySequence => { + const firstDay = daySequence[0]; + const lastDay = daySequence[daySequence.length - 1]; + return new Period(firstDay, lastDay); + }); +} + +export function getPeriodDifference(basePeriod: Period, periods: Period[]) { + const baseListOfDays = getPeriodAsListOfDays(basePeriod); + + const listOfDaysToExclude = periods.map(period => getPeriodAsListOfDays(period)).flat(); + + const daysToInclude = []; + let index = 0; + + baseListOfDays.forEach(currentDay => { + const currentDayShouldBeIncluded = !listOfDaysToExclude.includes(currentDay); + if (currentDayShouldBeIncluded) { + if (Array.isArray(daysToInclude[index])) { + daysToInclude[index].push(currentDay); + } else { + daysToInclude[index] = [currentDay]; + } + } else if (daysToInclude[index]) { + index += 1; + } + }); + + return getDaySequencesAsListOfPeriods(daysToInclude); +} + +export const beregnDagerTimer = (dur: string) => Math.round(dayjs.duration(dur).asHours() * 100) / 100; diff --git a/packages/fakta-etablert-tilsyn/src/util/formats.ts b/packages/fakta-etablert-tilsyn/src/util/formats.ts new file mode 100644 index 0000000000..fc76eb9e67 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/util/formats.ts @@ -0,0 +1,7 @@ +/* eslint-disable import/prefer-default-export */ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(utc); + +export const prettifyDate = (date: string) => dayjs(date).utc(true).format('DD.MM.YYYY'); diff --git a/packages/fakta-etablert-tilsyn/src/util/hooks.ts b/packages/fakta-etablert-tilsyn/src/util/hooks.ts new file mode 100644 index 0000000000..9ba0566b70 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/util/hooks.ts @@ -0,0 +1,10 @@ +/* eslint-disable import/prefer-default-export */ +import { useEffect, useRef } from 'react'; + +export const usePrevious = value => { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }); + return ref.current; +}; diff --git a/packages/fakta-etablert-tilsyn/src/util/periodUtils.ts b/packages/fakta-etablert-tilsyn/src/util/periodUtils.ts new file mode 100644 index 0000000000..c5833d16e7 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/util/periodUtils.ts @@ -0,0 +1,36 @@ +import { Period } from '@fpsak-frontend/utils'; +import { getPeriodDifference } from './dateUtils'; + +export const getStringMedPerioder = (perioder: Period[]): string => { + if (perioder.length === 1) { + return `perioden ${perioder[0].prettifyPeriod()}`; + } + + let perioderString = ''; + perioder.forEach((periode, index) => { + const prettyPeriod = periode.prettifyPeriod(); + if (index === 0) { + perioderString = prettyPeriod; + } else if (index === perioder.length - 1) { + perioderString = `${perioderString} og ${prettyPeriod}`; + } else { + perioderString = `${perioderString}, ${prettyPeriod}`; + } + }); + + return `periodene ${perioderString}`; +}; + +export const finnResterendePerioder = (perioderFraForm: Period[], periodeTilVurdering: Period): Period[] => { + const formatertePerioderFraForm = perioderFraForm.map(periode => { + if ((periode as any).period) { + return (periode as any).period; + } + return periode; + }); + const resterendePerioder = + formatertePerioderFraForm.length > 0 && + getPeriodDifference(periodeTilVurdering, formatertePerioderFraForm as Period[]); + + return resterendePerioder; +}; diff --git a/packages/fakta-etablert-tilsyn/src/util/renderers.tsx b/packages/fakta-etablert-tilsyn/src/util/renderers.tsx new file mode 100644 index 0000000000..aeecdfcca4 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/src/util/renderers.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import MainComponent from '../ui/MainComponent'; +import ContainerContract from '../types/ContainerContract'; + +function prepare() { + if (process.env.NODE_ENV !== 'production') { + return import('../mock/browser').then(({ worker }) => worker.start({ onUnhandledRequest: 'bypass' })); + } + + return Promise.resolve(); +} + +const renderAppInSuccessfulState = (appId: string, data: ContainerContract): Promise => + prepare().then(() => { + const container = document.getElementById(appId); + const root = createRoot(container); + root.render(); + }); + +export default { + renderAppInSuccessfulState, +}; diff --git a/packages/fakta-etablert-tilsyn/webpack/webpack-config.development.js b/packages/fakta-etablert-tilsyn/webpack/webpack-config.development.js new file mode 100644 index 0000000000..151f65b99d --- /dev/null +++ b/packages/fakta-etablert-tilsyn/webpack/webpack-config.development.js @@ -0,0 +1,40 @@ +/* eslint-disable import/extensions */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const webpack = require('webpack'); +const path = require('path'); +const { merge } = require('webpack-merge'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const WebpackDevServer = require('webpack-dev-server'); +const commonWebpackConfig = require('./webpack.common.js'); + +const webpackConfig = merge(commonWebpackConfig, { + entry: `${path.resolve(__dirname, '../', 'src')}/dev/app.ts`, + mode: 'development', + devtool: 'eval-cheap-module-source-map', + plugins: [ + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, '../index.html'), + }), + ], +}); + +const port = 8484; +const devServerOptions = { + hot: true, + headers: { + 'Access-Control-Allow-Origin': 'http://localhost:9000', + }, + port, +}; + +const compiler = webpack(webpackConfig); +const devServer = new WebpackDevServer(devServerOptions, compiler); +compiler.close(() => console.info('Compiler closed')); + +devServer.startCallback(error => { + if (error) { + console.error(error); + } else { + console.log(`Listening at port ${port}`); + } +}); diff --git a/packages/fakta-etablert-tilsyn/webpack/webpack.common.js b/packages/fakta-etablert-tilsyn/webpack/webpack.common.js new file mode 100644 index 0000000000..ffc2e21ed2 --- /dev/null +++ b/packages/fakta-etablert-tilsyn/webpack/webpack.common.js @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const process = require('process'); +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const cssExtractLoaderConfig = { + loader: MiniCssExtractPlugin.loader, +}; + +const nodeModules = path.resolve(__dirname, '../node_modules'); +const rootNodeModules = path.resolve(__dirname, '../../../node_modules'); + +module.exports = { + resolve: { + extensions: ['.ts', '.tsx', '.js', '.css'], + }, + module: { + rules: [ + { + test: /\.(ts|js)x?$/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + rootMode: 'upward', + }, + }, + }, + { + test: /\.css$/, + use: [ + cssExtractLoaderConfig, + { + loader: 'css-loader', + options: { + importLoaders: 1, + modules: { + localIdentName: '[name]_[local]_[contenthash:base64:5]', + }, + }, + }, + ], + exclude: [nodeModules, rootNodeModules], + }, + { + test: /\.(less|css)?$/, + use: [ + cssExtractLoaderConfig, + { + loader: 'css-loader', + }, + { + loader: 'less-loader', + options: { + lessOptions: { + modifyVars: { + nodeModulesPath: '~', + coreModulePath: '~', + }, + }, + }, + }, + ], + include: [nodeModules, rootNodeModules], + }, + { + test: /\.(jpg|png|svg)$/, + loader: 'file-loader', + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: 'styles.css', + }), + ], +}; diff --git a/packages/fakta-feilutbetaling/package.json b/packages/fakta-feilutbetaling/package.json index 778b412338..71182c0017 100644 --- a/packages/fakta-feilutbetaling/package.json +++ b/packages/fakta-feilutbetaling/package.json @@ -22,7 +22,7 @@ "nav-frontend-typografi-style": "2.0.2", "prop-types": "15.8.1", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/fakta-felles/package.json b/packages/fakta-felles/package.json index 81ebd18685..d1d683ccde 100644 --- a/packages/fakta-felles/package.json +++ b/packages/fakta-felles/package.json @@ -18,7 +18,7 @@ "nav-frontend-typografi": "4.0.2", "nav-frontend-typografi-style": "2.0.2", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10" diff --git a/packages/fakta-inntekt-og-ytelser/package.json b/packages/fakta-inntekt-og-ytelser/package.json index bc7e8ab3b9..2d7dff9add 100644 --- a/packages/fakta-inntekt-og-ytelser/package.json +++ b/packages/fakta-inntekt-og-ytelser/package.json @@ -15,7 +15,7 @@ "nav-frontend-typografi-style": "2.0.2", "prop-types": "15.8.1", "react": "18.2.0", - "react-intl": "6.5.2" + "react-intl": "6.5.5" }, "devDependencies": { "@fpsak-frontend/utils-test": "1.0.0", diff --git a/packages/fakta-inntektsmelding/index.html b/packages/fakta-inntektsmelding/index.html new file mode 100644 index 0000000000..ad9ecbcccc --- /dev/null +++ b/packages/fakta-inntektsmelding/index.html @@ -0,0 +1,113 @@ + + + Kompletthet + + + + +
+ + + diff --git a/packages/fakta-inntektsmelding/index.ts b/packages/fakta-inntektsmelding/index.ts new file mode 100644 index 0000000000..cd83995094 --- /dev/null +++ b/packages/fakta-inntektsmelding/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as Inntektsmelding } from './src/ui/MainComponent'; diff --git a/packages/fakta-inntektsmelding/mock/api-mock.ts b/packages/fakta-inntektsmelding/mock/api-mock.ts new file mode 100644 index 0000000000..1981e95cd7 --- /dev/null +++ b/packages/fakta-inntektsmelding/mock/api-mock.ts @@ -0,0 +1,9 @@ +/* eslint-disable import/prefer-default-export */ +import { rest } from 'msw'; +import mockedKompletthetsdata from './mockedKompletthetsdata'; + +export const handlers = [ + rest.get('http://localhost:8082/mock/kompletthet', (req, res, ctx) => + res(ctx.status(200), ctx.json(mockedKompletthetsdata)), + ), +]; diff --git a/packages/fakta-inntektsmelding/mock/inntektsmeldingPropsMock.ts b/packages/fakta-inntektsmelding/mock/inntektsmeldingPropsMock.ts new file mode 100644 index 0000000000..3202187dde --- /dev/null +++ b/packages/fakta-inntektsmelding/mock/inntektsmeldingPropsMock.ts @@ -0,0 +1,226 @@ +export default { + arbeidsforhold: { + 896929119: { + identifikator: '896929119', + personIdentifikator: null, + navn: 'SAUEFABRIKK', + fødselsdato: null, + arbeidsforholdreferanser: [ + { + internArbeidsforholdId: '99ab90b6-98fd-4ab8-8632-fc08d8cb898e', + eksternArbeidsforholdId: '2', + }, + ], + }, + 972674818: { + identifikator: '972674818', + personIdentifikator: null, + navn: 'PENGELØS SPAREBANK', + fødselsdato: null, + arbeidsforholdreferanser: [ + { + internArbeidsforholdId: 'b8c8b29f-42da-4aef-9714-6fbc9772079f', + eksternArbeidsforholdId: '1', + }, + ], + }, + }, + endpoints: { + kompletthetBeregning: 'tilstand', + }, + readOnly: false, + saksbehandlere: { + Z994142: 'F_Z994142 E_Z994142', + Z994895: 'F_Z994895 E_Z994895', + Z994158: 'F_Z994158 E_Z994158', + }, + aksjonspunkter: [ + { + aksjonspunktType: { + kode: 'MANU', + kodeverk: 'AKSJONSPUNKT_TYPE', + }, + begrunnelse: null, + besluttersBegrunnelse: 'evvv', + definisjon: { + skalAvbrytesVedTilbakeføring: false, + kode: '9069', + kodeverk: 'AKSJONSPUNKT_DEF', + }, + erAktivt: true, + fristTid: null, + kanLoses: true, + status: { + kode: 'OPPR', + kodeverk: 'AKSJONSPUNKT_STATUS', + }, + toTrinnsBehandling: true, + toTrinnsBehandlingGodkjent: false, + vilkarType: null, + vurderPaNyttArsaker: [ + { + kode: 'ANNET', + kodeverk: 'VURDER_AARSAK', + }, + ], + venteårsak: { + kanVelgesIGui: false, + kode: '-', + kodeverk: 'VENT_AARSAK', + }, + venteårsakVariant: null, + opprettetAv: 'srvk9sak', + }, + ], +}; + +export const aksjonspunkt9071Props = { + arbeidsforhold: { + 896929119: { + identifikator: '896929119', + personIdentifikator: null, + navn: 'SAUEFABRIKK', + fødselsdato: null, + arbeidsforholdreferanser: [ + { + internArbeidsforholdId: '99ab90b6-98fd-4ab8-8632-fc08d8cb898e', + eksternArbeidsforholdId: '2', + }, + ], + }, + 972674818: { + identifikator: '972674818', + personIdentifikator: null, + navn: 'PENGELØS SPAREBANK', + fødselsdato: null, + arbeidsforholdreferanser: [ + { + internArbeidsforholdId: 'b8c8b29f-42da-4aef-9714-6fbc9772079f', + eksternArbeidsforholdId: '1', + }, + ], + }, + }, + endpoints: { + kompletthetBeregning: 'tilstand', + }, + readOnly: false, + saksbehandlere: { + Z994142: 'F_Z994142 E_Z994142', + Z994895: 'F_Z994895 E_Z994895', + Z994158: 'F_Z994158 E_Z994158', + }, + aksjonspunkter: [ + { + aksjonspunktType: { + kode: 'MANU', + kodeverk: 'AKSJONSPUNKT_TYPE', + }, + begrunnelse: null, + besluttersBegrunnelse: 'evvv', + definisjon: { + skalAvbrytesVedTilbakeføring: false, + kode: '9071', + kodeverk: 'AKSJONSPUNKT_DEF', + }, + erAktivt: true, + fristTid: null, + kanLoses: true, + status: { + kode: 'OPPR', + kodeverk: 'AKSJONSPUNKT_STATUS', + }, + toTrinnsBehandling: true, + toTrinnsBehandlingGodkjent: false, + vilkarType: null, + vurderPaNyttArsaker: [ + { + kode: 'ANNET', + kodeverk: 'VURDER_AARSAK', + }, + ], + venteårsak: { + kanVelgesIGui: false, + kode: '-', + kodeverk: 'VENT_AARSAK', + }, + venteårsakVariant: null, + opprettetAv: 'srvk9sak', + }, + ], +}; +export const aksjonspunkt9071FerdigProps = { + arbeidsforhold: { + 896929119: { + identifikator: '896929119', + personIdentifikator: null, + navn: 'SAUEFABRIKK', + fødselsdato: null, + arbeidsforholdreferanser: [ + { + internArbeidsforholdId: '99ab90b6-98fd-4ab8-8632-fc08d8cb898e', + eksternArbeidsforholdId: '2', + }, + ], + }, + 972674818: { + identifikator: '972674818', + personIdentifikator: null, + navn: 'PENGELØS SPAREBANK', + fødselsdato: null, + arbeidsforholdreferanser: [ + { + internArbeidsforholdId: 'b8c8b29f-42da-4aef-9714-6fbc9772079f', + eksternArbeidsforholdId: '1', + }, + ], + }, + }, + endpoints: { + kompletthetBeregning: 'tilstand', + }, + readOnly: false, + saksbehandlere: { + Z994142: 'F_Z994142 E_Z994142', + Z994895: 'F_Z994895 E_Z994895', + Z994158: 'F_Z994158 E_Z994158', + }, + aksjonspunkter: [ + { + aksjonspunktType: { + kode: 'MANU', + kodeverk: 'AKSJONSPUNKT_TYPE', + }, + begrunnelse: null, + besluttersBegrunnelse: 'evvv', + definisjon: { + skalAvbrytesVedTilbakeføring: false, + kode: '9071', + kodeverk: 'AKSJONSPUNKT_DEF', + }, + erAktivt: true, + fristTid: null, + kanLoses: true, + status: { + kode: 'UTFORT', + kodeverk: 'AKSJONSPUNKT_STATUS', + }, + toTrinnsBehandling: true, + toTrinnsBehandlingGodkjent: false, + vilkarType: null, + vurderPaNyttArsaker: [ + { + kode: 'ANNET', + kodeverk: 'VURDER_AARSAK', + }, + ], + venteårsak: { + kanVelgesIGui: false, + kode: '-', + kodeverk: 'VENT_AARSAK', + }, + venteårsakVariant: null, + opprettetAv: 'srvk9sak', + }, + ], +}; diff --git a/packages/fakta-inntektsmelding/mock/mockedKompletthetsdata.ts b/packages/fakta-inntektsmelding/mock/mockedKompletthetsdata.ts new file mode 100644 index 0000000000..33743b8483 --- /dev/null +++ b/packages/fakta-inntektsmelding/mock/mockedKompletthetsdata.ts @@ -0,0 +1,208 @@ +import TilstandStatus from '../src/types/TilstandStatus'; + +export default { + tilstand: [ + { + periode: '2022-02-01/2022-02-16', + status: [ + { + arbeidsgiver: { arbeidsgiver: '896929119', arbeidsforhold: '2' }, + status: 'FORTSETT_UTEN', + journalpostId: null, + }, + { + arbeidsgiver: { arbeidsgiver: '896929119', arbeidsforhold: '1' }, + status: 'FORTSETT_UTEN', + journalpostId: null, + }, + ], + vurdering: { kode: 'FORTSETT', beskrivelse: 'Kan beregnes på bakgrunn av opplysninger fra a-ordningen' }, + tilVurdering: true, + begrunnelse: 'wwdwdwwqwdwqwqw', + }, + ], +}; + +export const manglerInntektsmelding = { + tilstand: [ + { + periode: '2022-02-01/2022-02-16', + status: [ + { + arbeidsgiver: { arbeidsgiver: '896929119', arbeidsforhold: '2' }, + status: TilstandStatus.MANGLER, + journalpostId: null, + }, + { + arbeidsgiver: { arbeidsgiver: '896929119', arbeidsforhold: '1' }, + status: TilstandStatus.MANGLER, + journalpostId: null, + }, + ], + vurdering: { + kode: '-', + beskrivelse: 'Udefinert', + }, + tilVurdering: true, + }, + ], +}; +export const manglerFlereInntektsmeldinger = { + tilstand: [ + { + periode: '2022-02-01/2022-02-16', + status: [ + { + arbeidsgiver: { arbeidsgiver: '896929119', arbeidsforhold: '2' }, + status: TilstandStatus.MANGLER, + journalpostId: null, + }, + { + arbeidsgiver: { arbeidsgiver: '896929119', arbeidsforhold: '1' }, + status: TilstandStatus.MANGLER, + journalpostId: null, + }, + ], + vurdering: { + kode: '-', + beskrivelse: 'Udefinert', + }, + tilVurdering: true, + }, + { + periode: '2022-03-01/2022-03-16', + status: [ + { + arbeidsgiver: { arbeidsgiver: '896929119', arbeidsforhold: '2' }, + status: TilstandStatus.MANGLER, + journalpostId: null, + }, + { + arbeidsgiver: { arbeidsgiver: '896929119', arbeidsforhold: '1' }, + status: TilstandStatus.MANGLER, + journalpostId: null, + }, + ], + vurdering: { + kode: '-', + beskrivelse: 'Udefinert', + }, + tilVurdering: true, + }, + ], +}; + +export const ikkePaakrevdOgManglerInntektsmelding = { + tilstand: [ + { + periode: '2022-02-01/2022-02-16', + status: [ + { + arbeidsgiver: { arbeidsgiver: '896929119', arbeidsforhold: '2' }, + status: TilstandStatus.MANGLER, + journalpostId: null, + }, + { + arbeidsgiver: { arbeidsgiver: '896929119', arbeidsforhold: '1' }, + status: TilstandStatus.MANGLER, + journalpostId: null, + }, + ], + vurdering: { + kode: '-', + beskrivelse: 'Udefinert', + }, + tilVurdering: true, + }, + { + periode: '2022-08-01/2022-08-04', + status: [ + { + arbeidsgiver: { + arbeidsgiver: '896929119', + arbeidsforhold: '2', + }, + status: 'IKKE_PÅKREVD', + journalpostId: null, + }, + { + arbeidsgiver: { + arbeidsgiver: '972674818', + arbeidsforhold: null, + }, + status: 'MOTTATT', + journalpostId: '573777857', + }, + ], + vurdering: { + kode: '-', + beskrivelse: 'Udefinert', + }, + tilVurdering: true, + begrunnelse: null, + }, + ], +}; + +export const ikkePaakrevd = { + tilstand: [ + { + periode: '2022-08-01/2022-08-04', + status: [ + { + arbeidsgiver: { + arbeidsgiver: '896929119', + arbeidsforhold: '2', + }, + status: 'IKKE_PÅKREVD', + journalpostId: null, + }, + { + arbeidsgiver: { + arbeidsgiver: '972674818', + arbeidsforhold: null, + }, + status: 'MOTTATT', + journalpostId: '573777857', + }, + ], + vurdering: { + kode: '-', + beskrivelse: 'Udefinert', + }, + tilVurdering: true, + begrunnelse: null, + }, + ], +}; +export const alleErMottatt = { + tilstand: [ + { + periode: '2022-08-01/2022-08-04', + status: [ + { + arbeidsgiver: { + arbeidsgiver: '896929119', + arbeidsforhold: '2', + }, + status: 'MOTTATT', + journalpostId: null, + }, + { + arbeidsgiver: { + arbeidsgiver: '972674818', + arbeidsforhold: null, + }, + status: 'MOTTATT', + journalpostId: '573777857', + }, + ], + vurdering: { + kode: '-', + beskrivelse: 'Udefinert', + }, + tilVurdering: true, + begrunnelse: null, + }, + ], +}; diff --git a/packages/fakta-inntektsmelding/package.json b/packages/fakta-inntektsmelding/package.json new file mode 100644 index 0000000000..9020a3b9ac --- /dev/null +++ b/packages/fakta-inntektsmelding/package.json @@ -0,0 +1,114 @@ +{ + "name": "@k9-sak-web/fakta-inntektsmelding", + "version": "1.0.0", + "module": "index.ts", + "private": true, + "scripts": { + "dev": "node webpack/webpack-config.development.js" + }, + "keywords": [], + "author": "NAV IT", + "license": "MIT", + "devDependencies": { + "@navikt/ds-css": "5.11.2", + "@navikt/ds-icons": "3.4.3", + "@navikt/ds-react": "5.11.2", + "@navikt/fnrvalidator": "1.3.3", + "@navikt/ft-form-hooks": "4.2.17", + "@navikt/ft-plattform-komponenter": "2.3.14", + "@popperjs/core": "2.11.8", + "@storybook/react": "7.5.2", + "@storybook/testing-library": "0.2.2", + "@storybook/testing-react": "2.0.1", + "@testing-library/react": "14.0.0", + "@testing-library/user-event": "14.5.1", + "@types/jest": "29.5.7", + "@types/node": "20.8.10", + "@types/react": "18.2.34", + "axios": "1.6.2", + "classnames": "2.3.2", + "cors": "2.8.5", + "date-fns": "2.30.0", + "dayjs": "1.11.10", + "express": "4.18.2", + "lodash.throttle": "4.1.1", + "msw": "1.3.2", + "msw-storybook-addon": "1.10.0", + "nav-frontend-alertstriper": "4.0.2", + "nav-frontend-alertstriper-style": "3.0.2", + "nav-frontend-chevron": "1.0.30", + "nav-frontend-chevron-style": "1.0.4", + "nav-frontend-core": "6.0.1", + "nav-frontend-ekspanderbartpanel": "4.0.4", + "nav-frontend-ekspanderbartpanel-style": "2.0.2", + "nav-frontend-ikoner-assets": "3.0.1", + "nav-frontend-ikonknapper": "2.1.3", + "nav-frontend-js-utils": "1.0.20", + "nav-frontend-knapper": "3.1.3", + "nav-frontend-knapper-style": "2.1.2", + "nav-frontend-lenker": "2.0.2", + "nav-frontend-lenker-style": "2.0.2", + "nav-frontend-paneler": "3.0.2", + "nav-frontend-paneler-style": "2.0.2", + "nav-frontend-skjema": "4.0.6", + "nav-frontend-skjema-style": "3.0.3", + "nav-frontend-spinner": "3.0.1", + "nav-frontend-spinner-style": "1.0.2", + "nav-frontend-typografi": "4.0.2", + "nav-frontend-typografi-style": "2.0.2", + "nodemon": "3.0.1", + "react": "18.2.0", + "react-collapse": "5.1.1", + "react-dom": "18.2.0", + "react-hook-form": "7.48.2", + "react-popper": "2.3.0", + "uuid": "9.0.1", + "webpack-node-externals": "^3.0.0" + }, + "peerDependencies": { + "@navikt/ds-css": "5.11.2", + "@navikt/ds-icons": "3.4.3", + "@navikt/ds-react": "5.11.2", + "@navikt/fnrvalidator": "1.3.3", + "@navikt/ft-form-hooks": "4.2.13", + "@navikt/ft-plattform-komponenter": "2.3.10", + "@popperjs/core": "2.11.8", + "axios": "1.6.2", + "classnames": "2.3.2", + "date-fns": "2.30.0", + "dayjs": "1.11.10", + "lodash.throttle": "4.1.1", + "msw-storybook-addon": "1.10.0", + "nav-frontend-alertstriper": "4.0.2", + "nav-frontend-alertstriper-style": "3.0.2", + "nav-frontend-chevron": "1.0.30", + "nav-frontend-chevron-style": "1.0.4", + "nav-frontend-core": "6.0.1", + "nav-frontend-ekspanderbartpanel": "4.0.4", + "nav-frontend-ekspanderbartpanel-style": "2.0.2", + "nav-frontend-ikoner-assets": "3.0.1", + "nav-frontend-ikonknapper": "2.1.3", + "nav-frontend-js-utils": "1.0.20", + "nav-frontend-knapper": "3.1.3", + "nav-frontend-knapper-style": "2.1.2", + "nav-frontend-lenker": "2.0.2", + "nav-frontend-lenker-style": "2.0.2", + "nav-frontend-paneler": "3.0.2", + "nav-frontend-paneler-style": "2.0.2", + "nav-frontend-skjema": "4.0.6", + "nav-frontend-skjema-style": "3.0.3", + "nav-frontend-spinner": "3.0.1", + "nav-frontend-spinner-style": "1.0.2", + "nav-frontend-typografi": "4.0.2", + "nav-frontend-typografi-style": "2.0.2", + "react": "18.2.0", + "react-collapse": "5.1.1", + "react-dom": "18.2.0", + "react-hook-form": "7.48.2", + "react-popper": "2.3.0", + "uuid": "9.0.1" + }, + "msw": { + "workerDirectory": "public" + } +} diff --git a/packages/fakta-inntektsmelding/src/context/ContainerContext.ts b/packages/fakta-inntektsmelding/src/context/ContainerContext.ts new file mode 100644 index 0000000000..80f73f7e84 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/context/ContainerContext.ts @@ -0,0 +1,5 @@ +import React from 'react'; +import ContainerContract from '../types/ContainerContract'; + +const ContainerContext = React.createContext(null); +export default ContainerContext; diff --git a/packages/fakta-inntektsmelding/src/dev/app.ts b/packages/fakta-inntektsmelding/src/dev/app.ts new file mode 100644 index 0000000000..d6a7666dad --- /dev/null +++ b/packages/fakta-inntektsmelding/src/dev/app.ts @@ -0,0 +1,13 @@ +import renderers from '../util/renderers'; +import ContainerContract from '../types/ContainerContract'; +import '@navikt/ft-plattform-komponenter/dist/style.css'; +import '@navikt/ds-css'; + +interface ExtendedWindow extends Window { + renderKompletthetApp: (id: string, contract: ContainerContract) => void; +} + +(window as Partial).renderKompletthetApp = async (appId, data) => { + const { renderAppInSuccessfulState } = renderers; + renderAppInSuccessfulState(appId, data); +}; diff --git a/packages/fakta-inntektsmelding/src/mock/browser.ts b/packages/fakta-inntektsmelding/src/mock/browser.ts new file mode 100644 index 0000000000..507d351d1b --- /dev/null +++ b/packages/fakta-inntektsmelding/src/mock/browser.ts @@ -0,0 +1,6 @@ +/* eslint-disable import/prefer-default-export */ +/* eslint-disable import/no-extraneous-dependencies */ +import { setupWorker } from 'msw'; +import { handlers } from '../../mock/api-mock'; +// This configures a Service Worker with the given request handlers. +export const worker = setupWorker(...handlers); diff --git a/packages/fakta-inntektsmelding/src/stories/MainComponent.stories.tsx b/packages/fakta-inntektsmelding/src/stories/MainComponent.stories.tsx new file mode 100644 index 0000000000..281308de2e --- /dev/null +++ b/packages/fakta-inntektsmelding/src/stories/MainComponent.stories.tsx @@ -0,0 +1,82 @@ +import { ComponentStory } from '@storybook/react'; +import { rest } from 'msw'; +import React from 'react'; +import inntektsmeldingPropsMock, { + aksjonspunkt9071FerdigProps, + aksjonspunkt9071Props, +} from '../../mock/inntektsmeldingPropsMock'; +import ferdigvisning, { + alleErMottatt, + ikkePaakrevd, + ikkePaakrevdOgManglerInntektsmelding, + manglerFlereInntektsmeldinger, + manglerInntektsmelding, +} from '../../mock/mockedKompletthetsdata'; +import MainComponent from '../ui/MainComponent'; + +export default { + args: inntektsmeldingPropsMock, + argTypes: { onFinished: { action: 'clicked' } }, + title: 'Inntektsmelding', + component: MainComponent, +}; + +const Template: ComponentStory = (args: any) => ; + +export const IkkePaakrevd = Template.bind({}); +export const Mangler9069 = Template.bind({}); +export const Mangler9071 = Template.bind({}); +export const ManglerFlere9071 = Template.bind({}); +export const IkkePaakrevdOgMangler9071 = Template.bind({}); +export const FerdigVisning9069 = Template.bind({}); +export const FerdigVisning9071 = Template.bind({}); +export const AlleInntektsmeldingerMottatt = Template.bind({}); + +IkkePaakrevd.args = inntektsmeldingPropsMock; +IkkePaakrevd.parameters = { + msw: { + handlers: [rest.get('/tilstand', (req, res, ctx) => res(ctx.json(ikkePaakrevd)))], + }, +}; + +Mangler9069.parameters = { + msw: { + handlers: [rest.get('/tilstand', (req, res, ctx) => res(ctx.json(manglerInntektsmelding)))], + }, +}; +Mangler9071.args = aksjonspunkt9071Props; +Mangler9071.parameters = { + msw: { + handlers: [rest.get('/tilstand', (req, res, ctx) => res(ctx.json(manglerInntektsmelding)))], + }, +}; + +ManglerFlere9071.args = aksjonspunkt9071Props; +ManglerFlere9071.parameters = { + msw: { + handlers: [rest.get('/tilstand', (req, res, ctx) => res(ctx.json(manglerFlereInntektsmeldinger)))], + }, +}; +IkkePaakrevdOgMangler9071.parameters = { + msw: { + handlers: [rest.get('/tilstand', (req, res, ctx) => res(ctx.json(ikkePaakrevdOgManglerInntektsmelding)))], + }, +}; +FerdigVisning9069.parameters = { + msw: { + handlers: [rest.get('/tilstand', (req, res, ctx) => res(ctx.json(ferdigvisning)))], + }, +}; +FerdigVisning9071.args = aksjonspunkt9071FerdigProps; +FerdigVisning9071.parameters = { + msw: { + handlers: [rest.get('/tilstand', (req, res, ctx) => res(ctx.json(ferdigvisning)))], + }, +}; + +AlleInntektsmeldingerMottatt.args = aksjonspunkt9071Props; +AlleInntektsmeldingerMottatt.parameters = { + msw: { + handlers: [rest.get('/tilstand', (req, res, ctx) => res(ctx.json(alleErMottatt)))], + }, +}; diff --git a/packages/fakta-inntektsmelding/src/stories/assets/code-brackets.svg b/packages/fakta-inntektsmelding/src/stories/assets/code-brackets.svg new file mode 100644 index 0000000000..73de947760 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/stories/assets/code-brackets.svg @@ -0,0 +1 @@ +illustration/code-brackets \ No newline at end of file diff --git a/packages/fakta-inntektsmelding/src/stories/assets/colors.svg b/packages/fakta-inntektsmelding/src/stories/assets/colors.svg new file mode 100644 index 0000000000..17d58d516e --- /dev/null +++ b/packages/fakta-inntektsmelding/src/stories/assets/colors.svg @@ -0,0 +1 @@ +illustration/colors \ No newline at end of file diff --git a/packages/fakta-inntektsmelding/src/stories/assets/comments.svg b/packages/fakta-inntektsmelding/src/stories/assets/comments.svg new file mode 100644 index 0000000000..6493a139f5 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/stories/assets/comments.svg @@ -0,0 +1 @@ +illustration/comments \ No newline at end of file diff --git a/packages/fakta-inntektsmelding/src/stories/assets/direction.svg b/packages/fakta-inntektsmelding/src/stories/assets/direction.svg new file mode 100644 index 0000000000..65676ac272 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/stories/assets/direction.svg @@ -0,0 +1 @@ +illustration/direction \ No newline at end of file diff --git a/packages/fakta-inntektsmelding/src/stories/assets/flow.svg b/packages/fakta-inntektsmelding/src/stories/assets/flow.svg new file mode 100644 index 0000000000..8ac27db403 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/stories/assets/flow.svg @@ -0,0 +1 @@ +illustration/flow \ No newline at end of file diff --git a/packages/fakta-inntektsmelding/src/stories/assets/plugin.svg b/packages/fakta-inntektsmelding/src/stories/assets/plugin.svg new file mode 100644 index 0000000000..29e5c690c0 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/stories/assets/plugin.svg @@ -0,0 +1 @@ +illustration/plugin \ No newline at end of file diff --git a/packages/fakta-inntektsmelding/src/stories/assets/repo.svg b/packages/fakta-inntektsmelding/src/stories/assets/repo.svg new file mode 100644 index 0000000000..f386ee902c --- /dev/null +++ b/packages/fakta-inntektsmelding/src/stories/assets/repo.svg @@ -0,0 +1 @@ +illustration/repo \ No newline at end of file diff --git a/packages/fakta-inntektsmelding/src/stories/assets/stackalt.svg b/packages/fakta-inntektsmelding/src/stories/assets/stackalt.svg new file mode 100644 index 0000000000..9b7ad27435 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/stories/assets/stackalt.svg @@ -0,0 +1 @@ +illustration/stackalt \ No newline at end of file diff --git a/packages/fakta-inntektsmelding/src/types/Aksjonspunkt.ts b/packages/fakta-inntektsmelding/src/types/Aksjonspunkt.ts new file mode 100644 index 0000000000..c6cde42f67 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/types/Aksjonspunkt.ts @@ -0,0 +1,17 @@ +import Kodeverk from './Kodeverk'; + +export type Aksjonspunkt = Readonly<{ + definisjon: Kodeverk; + status: Kodeverk; + begrunnelse?: string; + vilkarType?: Kodeverk; + toTrinnsBehandling?: boolean; + toTrinnsBehandlingGodkjent?: boolean; + vurderPaNyttArsaker?: Kodeverk[]; + besluttersBegrunnelse?: string; + aksjonspunktType?: Kodeverk; + kanLoses: boolean; + erAktivt: boolean; +}>; + +export default Aksjonspunkt; diff --git a/packages/fakta-inntektsmelding/src/types/AksjonspunktRequestPayload.ts b/packages/fakta-inntektsmelding/src/types/AksjonspunktRequestPayload.ts new file mode 100644 index 0000000000..9e95491324 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/types/AksjonspunktRequestPayload.ts @@ -0,0 +1,15 @@ +interface Perioder { + periode: string; + fortsett: boolean; + begrunnelse?: string; + kode: string; +} + +interface AksjonspunktRequestPayload { + begrunnelse?: string; + perioder: Perioder[]; + kode: string; + '@type': string; +} + +export default AksjonspunktRequestPayload; diff --git a/packages/fakta-inntektsmelding/src/types/ContainerContract.ts b/packages/fakta-inntektsmelding/src/types/ContainerContract.ts new file mode 100644 index 0000000000..73473e4d05 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/types/ContainerContract.ts @@ -0,0 +1,26 @@ +import Aksjonspunkt from './Aksjonspunkt'; +import AksjonspunktRequestPayload from './AksjonspunktRequestPayload'; + +export type ArbeidsgiverOpplysninger = Readonly<{ + navn: string; + fødselsdato?: string; +}>; + +export type DokumentOpplysninger = Readonly<{ + journalpostId: string; + href: string; +}>; + +interface ContainerContract { + readOnly: boolean; + arbeidsforhold: Record; + dokumenter?: DokumentOpplysninger[]; + httpErrorHandler?: (statusCode: number, locationHeader?: string) => void; + endpoints: { + kompletthetBeregning: string; + }; + onFinished: (data: AksjonspunktRequestPayload) => void; + aksjonspunkter: Aksjonspunkt[]; +} + +export default ContainerContract; diff --git a/packages/fakta-inntektsmelding/src/types/FieldName.ts b/packages/fakta-inntektsmelding/src/types/FieldName.ts new file mode 100644 index 0000000000..b4fcdd2fd8 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/types/FieldName.ts @@ -0,0 +1,6 @@ +enum FieldName { + BESLUTNING = 'beslutning', + BEGRUNNELSE = 'begrunnelse', +} + +export default FieldName; diff --git a/packages/fakta-inntektsmelding/src/types/Kodeverk.ts b/packages/fakta-inntektsmelding/src/types/Kodeverk.ts new file mode 100644 index 0000000000..2582deae05 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/types/Kodeverk.ts @@ -0,0 +1,6 @@ +export type Kodeverk = Readonly<{ + kode: string; + kodeverk: string; +}>; + +export default Kodeverk; diff --git a/packages/fakta-inntektsmelding/src/types/KompletthetData.ts b/packages/fakta-inntektsmelding/src/types/KompletthetData.ts new file mode 100644 index 0000000000..d654b50560 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/types/KompletthetData.ts @@ -0,0 +1,43 @@ +import { Period } from '@fpsak-frontend/utils'; +import TilstandStatus from './TilstandStatus'; + +export interface Kompletthet { + tilstand: Tilstand[]; +} + +export interface Tilstand { + periode: Period; + status: Status[]; + begrunnelse: string; + tilVurdering: boolean; + vurdering: Vurdering; + periodeOpprinneligFormat: string; +} + +export interface TilstandBeriket extends Tilstand { + redigeringsmodus: boolean; + setRedigeringsmodus: (state: boolean) => void; + beslutningFieldName?: string; + begrunnelseFieldName?: string; +} + +export interface Status { + arbeidsgiver: Arbeidsgiver; + status: TilstandStatus; + journalpostId: string; +} + +export interface Arbeidsgiver { + arbeidsgiver: string; + arbeidsforhold: null; +} +export interface Vurdering { + beskrivelse: string; + kode: Kode; +} + +export enum Kode { + FORTSETT = 'FORTSETT', + MANGLENDE_GRUNNLAG = 'MANGLENDE_GRUNNLAG', + TOM = '-', +} diff --git a/packages/fakta-inntektsmelding/src/types/KompletthetResponse.ts b/packages/fakta-inntektsmelding/src/types/KompletthetResponse.ts new file mode 100644 index 0000000000..ecbad1dc6c --- /dev/null +++ b/packages/fakta-inntektsmelding/src/types/KompletthetResponse.ts @@ -0,0 +1,12 @@ +import { Status, Vurdering } from './KompletthetData'; + +export interface Kompletthet { + tilstand: Tilstand[]; +} +export interface Tilstand { + periode: string; + status: Status[]; + tilVurdering: boolean; + begrunnelse: string; + vurdering: Vurdering; +} diff --git a/packages/fakta-inntektsmelding/src/types/TilstandStatus.ts b/packages/fakta-inntektsmelding/src/types/TilstandStatus.ts new file mode 100644 index 0000000000..57c5209836 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/types/TilstandStatus.ts @@ -0,0 +1,8 @@ +enum TilstandStatus { + MOTTATT = 'MOTTATT', + IKKE_PÅKREVD = 'IKKE_PÅKREVD', + FORTSETT_UTEN = 'FORTSETT_UTEN', + MANGLER = 'MANGLER', +} + +export default TilstandStatus; diff --git a/packages/fakta-inntektsmelding/src/ui/MainComponent.tsx b/packages/fakta-inntektsmelding/src/ui/MainComponent.tsx new file mode 100644 index 0000000000..c75f70b526 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/MainComponent.tsx @@ -0,0 +1,82 @@ +import { Period, get } from '@fpsak-frontend/utils'; +import { PageContainer } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import ContainerContext from '../context/ContainerContext'; +import ContainerContract from '../types/ContainerContract'; +import { Kompletthet as KompletthetData } from '../types/KompletthetData'; +import { Kompletthet as KompletthetResponse } from '../types/KompletthetResponse'; +import ActionType from './actionTypes'; +import Kompletthetsoversikt from './components/kompletthetsoversikt/Kompletthetsoversikt'; +import mainComponentReducer from './reducer'; + +function initKompletthetsdata({ tilstand }: KompletthetResponse): KompletthetData { + return { + tilstand: tilstand.map(({ periode, status, begrunnelse, tilVurdering, vurdering }) => { + const [fom, tom] = periode.split('/'); + return { + periode: new Period(fom, tom), + status, + begrunnelse, + tilVurdering, + vurdering, + periodeOpprinneligFormat: periode, + }; + }), + }; +} + +interface MainComponentProps { + data: ContainerContract; +} + +const MainComponent = ({ data }: MainComponentProps): JSX.Element => { + const [state, dispatch] = React.useReducer(mainComponentReducer, { + isLoading: true, + kompletthetsoversiktHarFeilet: false, + kompletthetsoversiktResponse: null, + }); + + const controller = React.useMemo(() => new AbortController(), []); + const { kompletthetsoversiktResponse, isLoading, kompletthetsoversiktHarFeilet } = state; + const { endpoints, onFinished, httpErrorHandler } = data; + + const getKompletthetsoversikt = () => + get(endpoints.kompletthetBeregning, httpErrorHandler, { + signal: controller.signal, + }); + + const handleError = () => { + dispatch({ type: ActionType.FAILED }); + }; + + React.useEffect(() => { + let isMounted = true; + getKompletthetsoversikt() + .then((response: KompletthetResponse) => { + if (isMounted) { + dispatch({ type: ActionType.OK, kompletthetsoversiktResponse: response }); + } + }) + .catch(handleError); + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + return ( + + + {kompletthetsoversiktResponse && ( + { + onFinished(payload); + }} + /> + )} + + + ); +}; + +export default MainComponent; diff --git a/packages/fakta-inntektsmelding/src/ui/actionTypes.ts b/packages/fakta-inntektsmelding/src/ui/actionTypes.ts new file mode 100644 index 0000000000..fe47614b80 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/actionTypes.ts @@ -0,0 +1,7 @@ +enum ActionType { + OK = 'OK', + FAILED = 'FAILED', + PENDING = 'PENDING', +} + +export default ActionType; diff --git a/packages/fakta-inntektsmelding/src/ui/components/arbeidsgiver-tekst/ArbeidsgiverTekst.tsx b/packages/fakta-inntektsmelding/src/ui/components/arbeidsgiver-tekst/ArbeidsgiverTekst.tsx new file mode 100644 index 0000000000..657231d6a6 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/arbeidsgiver-tekst/ArbeidsgiverTekst.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Arbeidsgiver } from '../../../types/KompletthetData'; +import ContainerContext from '../../../context/ContainerContext'; + +interface ArbeidsgiverTekstProps { + arbeidsgiver: Arbeidsgiver; +} + +const ArbeidsgiverTekst = ({ arbeidsgiver }: ArbeidsgiverTekstProps): JSX.Element => { + const { arbeidsforhold } = React.useContext(ContainerContext); + const id = arbeidsgiver.arbeidsgiver; + const tekst = arbeidsforhold[id].navn || arbeidsforhold[id].fødselsdato; + return ( + + {tekst} (Arbeidsforhold {arbeidsgiver.arbeidsforhold}) + + ); +}; + +export default ArbeidsgiverTekst; diff --git a/packages/fakta-inntektsmelding/src/ui/components/fortsett-uten-inntektsmelding-form/FortsettUtenInntektsmeldingForm.tsx b/packages/fakta-inntektsmelding/src/ui/components/fortsett-uten-inntektsmelding-form/FortsettUtenInntektsmeldingForm.tsx new file mode 100644 index 0000000000..2ce11706d6 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/fortsett-uten-inntektsmelding-form/FortsettUtenInntektsmeldingForm.tsx @@ -0,0 +1,192 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +import { Alert, Heading } from '@navikt/ds-react'; +import { Form, RadioGroupPanel, TextAreaField } from '@navikt/ft-form-hooks'; +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import { Hovedknapp, Knapp } from 'nav-frontend-knapper'; +import Panel from 'nav-frontend-paneler'; +import React from 'react'; +import { UseFormReturn } from 'react-hook-form'; +import ContainerContext from '../../../context/ContainerContext'; +import Aksjonspunkt from '../../../types/Aksjonspunkt'; +import AksjonspunktRequestPayload from '../../../types/AksjonspunktRequestPayload'; +import { Kode, TilstandBeriket } from '../../../types/KompletthetData'; +import TilstandStatus from '../../../types/TilstandStatus'; +import { skalVurderes } from '../../../util/utils'; +import styles from './fortsettUtenInntektsMeldingForm.css'; + +export interface FortsettUtenInntektsmeldingFormState { + begrunnelse: string; + beslutning: string; +} + +interface FortsettUtenInntektsmeldingFormProps { + tilstand: TilstandBeriket; + onSubmit: (payload: AksjonspunktRequestPayload) => void; + redigeringsmodus: boolean; + aksjonspunkt: Aksjonspunkt; + setRedigeringsmodus: (state: boolean) => void; + formMethods: UseFormReturn; + harFlereTilstanderTilVurdering: boolean; +} + +const FortsettUtenInntektsmeldingForm = ({ + onSubmit, + tilstand, + redigeringsmodus, + setRedigeringsmodus, + aksjonspunkt, + formMethods, + harFlereTilstanderTilVurdering, +}: FortsettUtenInntektsmeldingFormProps): JSX.Element => { + const { arbeidsforhold, readOnly } = React.useContext(ContainerContext); + + const { watch, reset } = formMethods; + const { beslutningFieldName, begrunnelseFieldName } = tilstand; + const beslutningId = `beslutning-${tilstand.periodeOpprinneligFormat}`; + const begrunnelseId = `begrunnelse-${tilstand.periodeOpprinneligFormat}`; + const beslutning = watch(beslutningFieldName); + const aksjonspunktKode = aksjonspunkt?.definisjon?.kode; + const vis = ((skalVurderes(tilstand) && !readOnly) || redigeringsmodus) && aksjonspunkt && tilstand.tilVurdering; + const skalViseBegrunnelse = !(aksjonspunktKode === '9069' && beslutning !== Kode.FORTSETT); + const fortsettKnappTekstFunc = { + '9069': (erFortsett: boolean) => + erFortsett ? 'Fortsett uten inntektsmelding' : 'Send purring med varsel om avslag', + '9071': (erFortsett: boolean) => (erFortsett ? 'Fortsett uten inntektsmelding' : 'Avslå periode'), + }; + const arbeidsgivereMedManglendeInntektsmelding = tilstand.status.filter(s => s.status === TilstandStatus.MANGLER); + + let arbeidsgivereString = ''; + const formatArbeidsgiver = arbeidsgiver => + `${arbeidsforhold[arbeidsgiver.arbeidsgiver]?.navn} (${arbeidsgiver.arbeidsforhold})`; + arbeidsgivereMedManglendeInntektsmelding.forEach(({ arbeidsgiver }, index) => { + if (index === 0) { + arbeidsgivereString = formatArbeidsgiver(arbeidsgiver); + } else if (index === arbeidsgivereMedManglendeInntektsmelding.length - 1) { + arbeidsgivereString = `${arbeidsgivereString} og ${formatArbeidsgiver(arbeidsgiver)}`; + } else { + arbeidsgivereString = `${arbeidsgivereString}, ${formatArbeidsgiver(arbeidsgiver)}`; + } + }); + + const submit = data => + onSubmit({ + '@type': aksjonspunktKode, + kode: aksjonspunktKode, + begrunnelse: skalViseBegrunnelse ? data[tilstand.begrunnelseFieldName] : null, + perioder: [ + { + begrunnelse: skalViseBegrunnelse ? data[tilstand.begrunnelseFieldName] : null, + periode: tilstand.periodeOpprinneligFormat, + fortsett: data[tilstand.beslutningFieldName] === Kode.FORTSETT, + kode: aksjonspunktKode, + }, + ], + }); + + const avbrytRedigering = () => { + reset(); + setRedigeringsmodus(false); + }; + + const radios = { + '9069': [ + { + value: Kode.FORTSETT, + label: `Ja, bruk A-inntekt for ${arbeidsgivereString}`, + id: `${beslutningId}${Kode.FORTSETT}`, + }, + { + value: Kode.MANGLENDE_GRUNNLAG, + label: 'Nei, send purring med varsel om avslag', + id: `${beslutningId}${Kode.MANGLENDE_GRUNNLAG}`, + }, + ], + '9071': [ + { + value: Kode.FORTSETT, + label: `Ja, bruk A-inntekt for ${arbeidsgivereString}`, + id: `${beslutningId}${Kode.FORTSETT}`, + }, + { + value: Kode.MANGLENDE_GRUNNLAG, + label: 'Nei, avslå periode på grunn av manglende inntektsopplysninger', + id: `${beslutningId}${Kode.MANGLENDE_GRUNNLAG}`, + }, + ], + }; + + if (!vis) { + return null; + } + + return ( + // eslint-disable-next-line react/jsx-props-no-spreading +
+ + + Kan du gå videre uten inntektsmelding? + + +
    +
  • + A-inntekt benyttes kun for de + arbeidsgiverne/arbeidsforholdene vi mangler inntektsmelding fra. +
  • +
  • + Refusjon i inntektsmeldinger vil alltid utbetales til arbeidsgiver. Evt. mellomlegg utbetales direkte til + søker. +
  • +
+
+
+ (!v ? 'Du må oppgi en verdi ' : null)]} + /> +
+ <> + {skalViseBegrunnelse && ( + + + {beslutning === Kode.FORTSETT && ( +
+ Vi benytter opplysninger fra A-inntekt for alle arbeidsgivere vi ikke har mottatt inntektsmelding + fra. Gjør en vurdering av hvorfor du benytter A-inntekt for å fastsette grunnlaget etter § 8-28. +
+ )} + {beslutning === Kode.MANGLENDE_GRUNNLAG && ( +
+ Skriv begrunnelse for hvorfor du ikke kan benytte opplysninger fra A-inntekt for å fastsette + grunnlaget, og avslå saken etter folketrygdloven §§ 21-3 og 8-28. +
+ )} + + } + validate={[v => (!v ? 'Du må fylle inn en verdi' : null)]} + /> + )} + +
+ {!harFlereTilstanderTilVurdering && !!beslutning && ( + {fortsettKnappTekstFunc[aksjonspunktKode](beslutning === Kode.FORTSETT)} + )} + {redigeringsmodus && ( + + Avbryt redigering + + )} +
+
+ +
+
+ ); +}; + +export default FortsettUtenInntektsmeldingForm; diff --git a/packages/fakta-inntektsmelding/src/ui/components/fortsett-uten-inntektsmelding-form/fortsettUtenInntektsMeldingForm.css b/packages/fakta-inntektsmelding/src/ui/components/fortsett-uten-inntektsmelding-form/fortsettUtenInntektsMeldingForm.css new file mode 100644 index 0000000000..8a542eb250 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/fortsett-uten-inntektsmelding-form/fortsettUtenInntektsMeldingForm.css @@ -0,0 +1,48 @@ +.fortsettUtenInntektsmelding__panel { + border: 2px solid #ff9100; + padding: 1.5625rem; + margin: 1.5625rem 0 0.6875rem 0; +} + +.fortsettUtenInntektsmelding__knapper { + display: flex; + gap: 1rem; +} + +.fortsettUtenInntektsmelding__begrunnelse-subtext { + font-weight: 400; +} + +.fortsettUtenInntektsmelding__radiogroup { + margin: 1rem 0; +} + +.fortsettUtenInntektsmelding__radiogroup :global(legend) { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} + +.fortsettUtenInntektsmelding__radiogroupAlert { + margin-top: 1rem; +} + +.fortsettUtenInntektsmelding__radiogroupAlert ul { + list-style-type: disc; + margin: 0; + padding-left: 1.5rem; +} + +.fortsettUtenInntektsmelding__radiogroupAlert :global(.navds-alert__wrapper) { + max-width: none; +} + +.radiogroupAlert__emphasize { + font-weight: bold; + text-decoration: underline; +} diff --git a/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-liste-heading/InntektsmeldingListeHeading.tsx b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-liste-heading/InntektsmeldingListeHeading.tsx new file mode 100644 index 0000000000..1060ecc704 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-liste-heading/InntektsmeldingListeHeading.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import ListItem from '../list-item/ListItem'; + +const firstColumnRenderer = () => Arbeidsgiver; +const secondColumnRenderer = () => Status inntektsmelding; + +const InntektsmeldingListeHeading = (): JSX.Element => ( + +); + +export default InntektsmeldingListeHeading; diff --git a/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-liste/InntektsmeldingListe.tsx b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-liste/InntektsmeldingListe.tsx new file mode 100644 index 0000000000..6e4550f45b --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-liste/InntektsmeldingListe.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Status } from '../../../types/KompletthetData'; +import InntektsmeldingMottattItem from '../inntektsmelding-mottatt-item/InntektsmeldingMottattItem'; +import InntektsmeldingAdvarsel from '../inntektsmelding-mangler-item/InntektsmeldingAdvarselItem'; +import styles from './inntektsmeldingListe.css'; + +interface PeriodListItemProps { + status: Status[]; +} + +const RenderListItem = ({ status }: { status: Status }) => { + const listItem = children => ( +
  • + {children} +
  • + ); + if (status.status === 'MOTTATT') { + return listItem(); + } + + return listItem(); +}; + +const InntektsmeldingListe = ({ status }: PeriodListItemProps): JSX.Element => ( +
      + {status.map((v, index) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} +
    +); + +export default InntektsmeldingListe; diff --git a/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-liste/inntektsmeldingListe.css b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-liste/inntektsmeldingListe.css new file mode 100644 index 0000000000..603f3eb348 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-liste/inntektsmeldingListe.css @@ -0,0 +1,9 @@ +.inntektsmeldingListe { + list-style-type: none; + margin: 0; + padding: 0; +} + +.inntektsmeldingListe__item { + margin-top: 0.75rem; +} diff --git a/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-mangler-item/InntektsmeldingAdvarselItem.tsx b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-mangler-item/InntektsmeldingAdvarselItem.tsx new file mode 100644 index 0000000000..ff290f5442 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-mangler-item/InntektsmeldingAdvarselItem.tsx @@ -0,0 +1,37 @@ +import { WarningIcon } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import { Status } from '../../../types/KompletthetData'; +import ListItem from '../list-item/ListItem'; +import styles from '../inntektsmelding-mottatt-item/inntektsmeldingMottattItem.css'; +import ArbeidsgiverTekst from '../arbeidsgiver-tekst/ArbeidsgiverTekst'; + +interface InntektsmeldingMottattItemProps { + status: Status; +} + +interface ManglerContentProps { + tekst: string; +} + +const ManglerContent = ({ tekst }: ManglerContentProps) => ( +
    + + {tekst} +
    +); + +const tekster = { + IKKE_PÅKREVD: 'Ikke påkrevd', + MANGLER: 'Mangler', + MOTTATT_IKKE_ANSATT: 'Mottatt, men ikke ansatt', + MOTTATT_UKJENT_ARBEIDSFORHOLDSID: 'Mottatt, men inneholder ukjent arbeidsforhold-ID', +}; + +const InntektsmeldingAdvarselItem = ({ status }: InntektsmeldingMottattItemProps): JSX.Element => { + const tekst = tekster[status.status]; + const firstColumnRenderer = () => ; + const secondColumnRenderer = () => ; + return ; +}; + +export default InntektsmeldingAdvarselItem; diff --git a/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-mottatt-item/InntektsmeldingMottattItem.tsx b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-mottatt-item/InntektsmeldingMottattItem.tsx new file mode 100644 index 0000000000..7f06497a13 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-mottatt-item/InntektsmeldingMottattItem.tsx @@ -0,0 +1,42 @@ +import { GreenCheckIconFilled } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import Lenke from 'nav-frontend-lenker'; +import { Status } from '../../../types/KompletthetData'; +import ListItem from '../list-item/ListItem'; +import styles from './inntektsmeldingMottattItem.css'; +import ArbeidsgiverTekst from '../arbeidsgiver-tekst/ArbeidsgiverTekst'; +import ContainerContext from '../../../context/ContainerContext'; +import { DokumentOpplysninger } from '../../../types/ContainerContract'; + +interface MottattContentProps { + dokumentLink: string; +} + +const MottattContent = ({ dokumentLink }: MottattContentProps) => ( +
    + +
    + Mottatt + + Vis inntektsmelding + +
    +
    +); + +interface InntektsmeldingMottattItemProps { + status: Status; +} + +const finnDokumentLink = (dokumenter: DokumentOpplysninger[], journalpostId: string) => + dokumenter.find(dokument => dokument.journalpostId === journalpostId); + +const InntektsmeldingMottattItem = ({ status }: InntektsmeldingMottattItemProps): JSX.Element => { + const { dokumenter } = React.useContext(ContainerContext); + const dokumentLink = finnDokumentLink(dokumenter || [], status.journalpostId)?.href; + const firstColumnRenderer = () => ; + const secondColumnRenderer = () => ; + return ; +}; + +export default InntektsmeldingMottattItem; diff --git a/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-mottatt-item/inntektsmeldingMottattItem.css b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-mottatt-item/inntektsmeldingMottattItem.css new file mode 100644 index 0000000000..45b0c4d771 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/inntektsmelding-mottatt-item/inntektsmeldingMottattItem.css @@ -0,0 +1,16 @@ +.mottattLabel { + display: flex; +} + +.mottattLabel__text { + margin-left: 0.5rem; +} + +.mottattLabel__link { + margin-left: 1rem; +} + +.mottattLabel svg { + width: 20px; + height: 20px; +} diff --git a/packages/fakta-inntektsmelding/src/ui/components/kompletthetsoversikt/InntektsmeldingManglerInfo.tsx b/packages/fakta-inntektsmelding/src/ui/components/kompletthetsoversikt/InntektsmeldingManglerInfo.tsx new file mode 100644 index 0000000000..c63d952667 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/kompletthetsoversikt/InntektsmeldingManglerInfo.tsx @@ -0,0 +1,66 @@ +import React from 'react'; + +import { Accordion, Alert, BodyLong, Heading } from '@navikt/ds-react'; +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import styles from './kompletthetsoversikt.css'; + +const InntektsmeldingManglerInfo = (): JSX.Element => ( + <> + + + + Vurder om du kan fortsette behandlingen uten inntektsmelding. + + + Inntektsmelding mangler for en eller flere arbeidsgivere, eller for ett eller flere arbeidsforhold hos samme + arbeidsgiver. + + + + + + + + + + Når kan du gå videre uten inntektsmelding? + + + + Vurder om du kan gå videre uten alle inntektsmeldinger hvis: +
      +
    • Det er rapportert fast og regelmessig lønn de siste 3 månedene før skjæringstidspunktet.
    • +
    • + Det ikke er rapportert lønn hos arbeidsforholdet de siste 3 månedene før skjæringstidspunktet. + Beregningsgrunnlaget for denne arbeidsgiveren vil settes til 0,-. +
    • +
    • + Måneden for skjæringstidspunktet er innrapportert til A-ordningen. Hvis det er innrapportert lavere + lønn enn foregående måneder kan det tyde på at arbeidsgiver ikke lenger utbetaler lønn. Det er dermed + lavere risiko for at arbeidsgiver vil kreve refusjon. +
    • +
    + + Du bør ikke gå videre uten inntektsmelding hvis: +
      +
    • + Det er arbeidsforhold og frilansoppdrag i samme organisasjon (sjekk i Aa-registeret). I disse + tilfellene trenger vi inntektsmelding for å skille hva som er arbeidsinntekt og frilansinntekt i + samme organisasjon. +
    • +
    • + Måneden for skjæringstidspunktet er innrapportert til A-ordningen, og det er utbetalt full lønn. + Dette tyder på at arbeidsgiver forskutterer lønn og vil kreve refusjon. I disse tilfellene bør vi + ikke utbetale til bruker, men vente på inntektsmelding. +
    • +
    +
    +
    +
    +
    +
    +
    + +); + +export default InntektsmeldingManglerInfo; diff --git a/packages/fakta-inntektsmelding/src/ui/components/kompletthetsoversikt/Kompletthetsoversikt.tsx b/packages/fakta-inntektsmelding/src/ui/components/kompletthetsoversikt/Kompletthetsoversikt.tsx new file mode 100644 index 0000000000..fbd26d6aec --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/kompletthetsoversikt/Kompletthetsoversikt.tsx @@ -0,0 +1,127 @@ +import React, { useState } from 'react'; +import { Period } from '@fpsak-frontend/utils'; +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import { Hovedknapp } from 'nav-frontend-knapper'; +import { useForm } from 'react-hook-form'; +import ContainerContext from '../../../context/ContainerContext'; +import { Kode, Kompletthet, Tilstand } from '../../../types/KompletthetData'; +import InntektsmeldingListeHeading from '../inntektsmelding-liste-heading/InntektsmeldingListeHeading'; +import InntektsmeldingListe from '../inntektsmelding-liste/InntektsmeldingListe'; +import PeriodList from '../period-list/PeriodList'; +import styles from './kompletthetsoversikt.css'; +import { + finnAktivtAksjonspunkt, + finnTilstanderSomRedigeres, + finnTilstanderSomVurderes, + ingenTilstanderHarMangler, +} from '../../../util/utils'; +import FieldName from '../../../types/FieldName'; +import InntektsmeldingManglerInfo from './InntektsmeldingManglerInfo'; +import AksjonspunktRequestPayload from '../../../types/AksjonspunktRequestPayload'; + +interface KompletthetsoversiktProps { + kompletthetsoversikt: Kompletthet; + onFormSubmit: (payload: AksjonspunktRequestPayload) => void; +} + +const Kompletthetsoversikt = ({ kompletthetsoversikt, onFormSubmit }: KompletthetsoversiktProps): JSX.Element => { + const { aksjonspunkter, readOnly } = React.useContext(ContainerContext); + const { tilstand: tilstander } = kompletthetsoversikt; + + const periods = tilstander.map(({ periode }) => periode); + const statuses = tilstander.map(({ status }) => status); + const aktivtAksjonspunkt = finnAktivtAksjonspunkt(aksjonspunkter); + const forrigeAksjonspunkt = aksjonspunkter.sort((a, b) => Number(b.definisjon.kode) - Number(a.definisjon.kode))[0]; + const aktivtAksjonspunktKode = aktivtAksjonspunkt?.definisjon?.kode; + const forrigeAksjonspunktKode = forrigeAksjonspunkt?.definisjon?.kode; + const aksjonspunktKode = aktivtAksjonspunktKode || forrigeAksjonspunktKode; + + const tilstanderBeriket = tilstander.map(tilstand => { + const [redigeringsmodus, setRedigeringsmodus] = useState(false); + + return { + ...tilstand, + redigeringsmodus, + setRedigeringsmodus, + begrunnelseFieldName: `${FieldName.BEGRUNNELSE}${tilstand.periodeOpprinneligFormat}`, + beslutningFieldName: `${FieldName.BESLUTNING}${tilstand.periodeOpprinneligFormat}`, + }; + }); + + const reducer = (defaultValues, tilstand: Tilstand) => ({ + ...defaultValues, + [`${FieldName.BEGRUNNELSE}${tilstand.periodeOpprinneligFormat}`]: tilstand?.begrunnelse || '', + [`${FieldName.BESLUTNING}${tilstand.periodeOpprinneligFormat}`]: null, + }); + const formMethods = useForm({ + mode: 'onTouched', + defaultValues: tilstanderBeriket.reduce(reducer, {}), + }); + const { isDirty } = formMethods.formState; + const { handleSubmit, watch } = formMethods; + + const tilstanderTilVurdering = [ + ...finnTilstanderSomVurderes(tilstanderBeriket), + ...finnTilstanderSomRedigeres(tilstanderBeriket), + ]; + + const harFlereTilstanderTilVurdering = tilstanderTilVurdering.length > 1; + const kanSendeInn = () => { + if (harFlereTilstanderTilVurdering || ingenTilstanderHarMangler(tilstanderBeriket)) { + if (!readOnly) { + if (aktivtAksjonspunktKode || (forrigeAksjonspunktKode && isDirty)) return true; + } + } + return false; + }; + + const listItemRenderer = (period: Period) => ; + const listHeadingRenderer = () => ; + return ( +
    +

    Inntektsmelding

    +

    Opplysninger til beregning

    + + + + + {kanSendeInn() && ( + +
    { + const perioder = tilstanderTilVurdering.map(tilstand => { + const skalViseBegrunnelse = !( + aksjonspunktKode === '9069' && watch(tilstand.beslutningFieldName) !== Kode.FORTSETT + ); + const begrunnelse = skalViseBegrunnelse ? data[tilstand.begrunnelseFieldName] : null; + return { + begrunnelse, + periode: tilstand.periodeOpprinneligFormat, + fortsett: data[tilstand.beslutningFieldName] === Kode.FORTSETT, + kode: aksjonspunktKode, + }; + }); + onFormSubmit({ + '@type': aksjonspunktKode, + kode: aksjonspunktKode, + perioder, + }); + })} + > + Send inn +
    +
    + )} +
    + ); +}; + +export default Kompletthetsoversikt; diff --git a/packages/fakta-inntektsmelding/src/ui/components/kompletthetsoversikt/kompletthetsoversikt.css b/packages/fakta-inntektsmelding/src/ui/components/kompletthetsoversikt/kompletthetsoversikt.css new file mode 100644 index 0000000000..34a9bb2584 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/kompletthetsoversikt/kompletthetsoversikt.css @@ -0,0 +1,42 @@ +.alert :global(.navds-heading) { + margin-bottom: 0; + margin-top: 0.125rem; +} + +.alert :global(.navds-alert__wrapper) { + max-width: none; +} + +.alertAccordion :global(button.navds-accordion__header) { + border: none; + padding: 0; + box-shadow: none; + margin-top: 0.25rem; +} + +.alertAccordion :global(.navds-accordion__content) { + border: none; +} + +h3.alertAccordion__heading:global(.navds-heading--xsmall) { + font-weight: var(--navds-font-weight-regular); + margin-bottom: 0; +} + +.kompletthet__mainHeading { + font-size: 1.375rem; +} + +.kompletthet__subHeading { + font-size: 1.125rem; + margin: 1.25rem 0 1.625rem; +} + +.kompletthet__list { + margin: 0; + padding-left: 1.5rem; +} + +.kompletthet--margin-top { + margin-top: 1.5rem; +} diff --git a/packages/fakta-inntektsmelding/src/ui/components/list-item/ListItem.tsx b/packages/fakta-inntektsmelding/src/ui/components/list-item/ListItem.tsx new file mode 100644 index 0000000000..78fc368dc1 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/list-item/ListItem.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import styles from './listItem.css'; + +interface ListItemProps { + firstColumnRenderer: () => React.ReactNode; + secondColumnRenderer: () => React.ReactNode; +} + +const ListItem = ({ firstColumnRenderer, secondColumnRenderer }: ListItemProps): JSX.Element => ( +
    +
    {firstColumnRenderer()}
    +
    {secondColumnRenderer()}
    +
    +); + +export default ListItem; diff --git a/packages/fakta-inntektsmelding/src/ui/components/list-item/listItem.css b/packages/fakta-inntektsmelding/src/ui/components/list-item/listItem.css new file mode 100644 index 0000000000..8f0123553f --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/list-item/listItem.css @@ -0,0 +1,7 @@ +.listItem { + display: flex; +} + +.listItem__firstColumn { + flex-basis: 35%; +} diff --git a/packages/fakta-inntektsmelding/src/ui/components/period-list/FortsettUtenInntektsmeldingAvslag.tsx b/packages/fakta-inntektsmelding/src/ui/components/period-list/FortsettUtenInntektsmeldingAvslag.tsx new file mode 100644 index 0000000000..822d88baa6 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/period-list/FortsettUtenInntektsmeldingAvslag.tsx @@ -0,0 +1,40 @@ +import { Edit } from '@navikt/ds-icons'; +import { Alert } from '@navikt/ds-react'; +import { LabelledContent } from '@navikt/ft-plattform-komponenter'; +import { Knapp } from 'nav-frontend-knapper'; +import React from 'react'; +import ContainerContext from '../../../context/ContainerContext'; +import { Kode, Tilstand } from '../../../types/KompletthetData'; +import styles from './periodList.css'; + +const FortsettUtenInntektsmeldingAvslag = ({ + tilstand, + redigeringsmodus, + setRedigeringsmodus, +}: { + tilstand: Tilstand; + redigeringsmodus: boolean; + setRedigeringsmodus: (state: boolean) => void; +}): JSX.Element | null => { + const { readOnly } = React.useContext(ContainerContext); + + if (tilstand?.vurdering?.kode === Kode.MANGLENDE_GRUNNLAG && !redigeringsmodus && tilstand.tilVurdering) { + return ( + <> + + Kan ikke gå videre uten inntektsmelding, søknad avslås. + {!readOnly && ( + setRedigeringsmodus(true)}> + + Rediger vurdering + + )} + + {tilstand.begrunnelse}} /> + + ); + } + return null; +}; + +export default FortsettUtenInntektsmeldingAvslag; diff --git a/packages/fakta-inntektsmelding/src/ui/components/period-list/FortsettUtenInntektsmeldingInfo.tsx b/packages/fakta-inntektsmelding/src/ui/components/period-list/FortsettUtenInntektsmeldingInfo.tsx new file mode 100644 index 0000000000..fe4af70150 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/period-list/FortsettUtenInntektsmeldingInfo.tsx @@ -0,0 +1,41 @@ +import { Edit } from '@navikt/ds-icons'; +import { Alert } from '@navikt/ds-react'; +import { LabelledContent } from '@navikt/ft-plattform-komponenter'; +import { Knapp } from 'nav-frontend-knapper'; +import React from 'react'; +import ContainerContext from '../../../context/ContainerContext'; +import { Kode, Tilstand } from '../../../types/KompletthetData'; +import styles from './periodList.css'; + +const FortsettUtenInntektsmeldingInfo = ({ + tilstand, + redigeringsmodus, + setRedigeringsmodus, +}: { + tilstand: Tilstand; + redigeringsmodus: boolean; + setRedigeringsmodus: (state: boolean) => void; +}): JSX.Element | null => { + const { readOnly } = React.useContext(ContainerContext); + + if (tilstand?.vurdering?.kode === Kode.FORTSETT && !redigeringsmodus && tilstand.tilVurdering) { + return ( + <> + + Fortsett uten inntektsmelding. + {!readOnly && ( + setRedigeringsmodus(true)}> + + Rediger vurdering + + )} + + {tilstand.begrunnelse}} /> + + ); + } + + return null; +}; + +export default FortsettUtenInntektsmeldingInfo; diff --git a/packages/fakta-inntektsmelding/src/ui/components/period-list/PeriodList.tsx b/packages/fakta-inntektsmelding/src/ui/components/period-list/PeriodList.tsx new file mode 100644 index 0000000000..02392e7839 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/period-list/PeriodList.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Period } from '@fpsak-frontend/utils'; +import { CalendarIcon } from '@navikt/ft-plattform-komponenter'; +import { UseFormReturn } from 'react-hook-form'; +import styles from './periodList.css'; +import FortsettUtenInntektsmeldingForm from '../fortsett-uten-inntektsmelding-form/FortsettUtenInntektsmeldingForm'; +import { TilstandBeriket } from '../../../types/KompletthetData'; +import FortsettUtenInntektsmeldingInfo from './FortsettUtenInntektsmeldingInfo'; +import FortsettUtenInntektsmeldingAvslag from './FortsettUtenInntektsmeldingAvslag'; +import Aksjonspunkt from '../../../types/Aksjonspunkt'; +import AksjonspunktRequestPayload from '../../../types/AksjonspunktRequestPayload'; +import { sorterSkjæringstidspunkt } from '../../../util/utils'; + +interface PeriodListProps { + tilstander: TilstandBeriket[]; + listHeadingRenderer: () => React.ReactNode; + listItemRenderer: (period: Period) => React.ReactNode; + onFormSubmit: (payload: AksjonspunktRequestPayload) => void; + aksjonspunkt: Aksjonspunkt; + formMethods: UseFormReturn; + harFlereTilstanderTilVurdering: boolean; +} + +const PeriodList = ({ + tilstander, + listHeadingRenderer, + listItemRenderer, + onFormSubmit, + aksjonspunkt, + formMethods, + harFlereTilstanderTilVurdering, +}: PeriodListProps): JSX.Element => ( +
      + {tilstander.sort(sorterSkjæringstidspunkt).map(tilstand => ( +
    • +
      + + {tilstand.periode.prettifyPeriod()} +
      + {listHeadingRenderer()} + {listItemRenderer(tilstand.periode)} + + + +
    • + ))} +
    +); + +export default PeriodList; diff --git a/packages/fakta-inntektsmelding/src/ui/components/period-list/periodList.css b/packages/fakta-inntektsmelding/src/ui/components/period-list/periodList.css new file mode 100644 index 0000000000..e4f05cd49d --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/period-list/periodList.css @@ -0,0 +1,38 @@ +.periodList { + margin: 0; + padding: 0; + list-style-type: none; +} + +.periodList__element { + border-bottom: 1px solid #b0b0b0; + padding: 1.5rem 0 2.1875rem 0; +} + +.periodList__element:first-of-type { + border-top: 1px solid #b0b0b0; +} + +.periodList__element__title { + display: flex; + margin-bottom: 1rem; +} + +.periodList__element__title__period { + position: relative; + top: 2px; +} + +.periodList__alertstripe { + margin: 2.4375rem 0 1.6875rem 0; +} + +.periodList__alertstripe div { + max-width: unset !important; + width: 100%; +} + +.periodList__alertstripe div button { + float: right; + margin-right: 1.8rem; +} diff --git a/packages/fakta-inntektsmelding/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx b/packages/fakta-inntektsmelding/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx new file mode 100644 index 0000000000..7df06ca407 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import ContainerContext from '../../../context/ContainerContext'; + +interface WriteAccessBoundContentProps { + contentRenderer: () => JSX.Element; +} + +const WriteAccessBoundContent = ({ contentRenderer }: WriteAccessBoundContentProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + if (readOnly === false) { + return contentRenderer(); + } + return null; +}; + +export default WriteAccessBoundContent; diff --git a/packages/fakta-inntektsmelding/src/ui/form/Checkbox.tsx b/packages/fakta-inntektsmelding/src/ui/form/Checkbox.tsx new file mode 100644 index 0000000000..a89c168a1d --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/form/Checkbox.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { Control, Controller } from 'react-hook-form'; +import { Checkbox } from 'nav-frontend-skjema'; + +interface Props { + control: Control; + name: string; + label: string; + disabled?: boolean; +} + +const ControlledCheckbox = ({ control, name, label, disabled }: Props): JSX.Element => ( + ( + field.onChange(e.target.checked)} ref={field.ref} disabled={disabled} /> + )} + /> +); + +export default ControlledCheckbox; diff --git a/packages/fakta-inntektsmelding/src/ui/reducer.ts b/packages/fakta-inntektsmelding/src/ui/reducer.ts new file mode 100644 index 0000000000..c281c0ebe7 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/ui/reducer.ts @@ -0,0 +1,40 @@ +import ActionType from './actionTypes'; +import { Kompletthet } from '../types/KompletthetResponse'; + +interface MainComponentState { + kompletthetsoversiktResponse: Kompletthet; + kompletthetsoversiktHarFeilet: boolean; + isLoading: boolean; +} + +interface Action { + type: ActionType; + kompletthetsoversiktResponse?: Kompletthet; +} + +const mainComponentReducer = (state: MainComponentState, action: Action): Partial => { + switch (action.type) { + case ActionType.OK: + return { + kompletthetsoversiktResponse: action.kompletthetsoversiktResponse, + kompletthetsoversiktHarFeilet: false, + isLoading: false, + }; + case ActionType.FAILED: + return { + kompletthetsoversiktResponse: null, + kompletthetsoversiktHarFeilet: true, + isLoading: false, + }; + case ActionType.PENDING: + return { + kompletthetsoversiktResponse: null, + kompletthetsoversiktHarFeilet: false, + isLoading: true, + }; + default: + return { ...state }; + } +}; + +export default mainComponentReducer; diff --git a/packages/fakta-inntektsmelding/src/util/renderers.tsx b/packages/fakta-inntektsmelding/src/util/renderers.tsx new file mode 100644 index 0000000000..5b707db862 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/util/renderers.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import MainComponent from '../ui/MainComponent'; +import ContainerContract from '../types/ContainerContract'; + +function prepare() { + if (process.env.NODE_ENV !== 'production') { + return import('../mock/browser').then(({ worker }) => worker.start({ onUnhandledRequest: 'bypass' })); + } + + return Promise.resolve(); +} + +const renderAppInSuccessfulState = (appId: string, data: ContainerContract): void => { + prepare().then(() => { + const container = document.getElementById(appId); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const root = createRoot(container!); + root.render(); + }); +}; +export default { + renderAppInSuccessfulState, +}; diff --git a/packages/fakta-inntektsmelding/src/util/tilstandManglerInntektsmelding.ts b/packages/fakta-inntektsmelding/src/util/tilstandManglerInntektsmelding.ts new file mode 100644 index 0000000000..a0a52c5224 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/util/tilstandManglerInntektsmelding.ts @@ -0,0 +1,5 @@ +import { Tilstand as TilstandResponse } from '../types/KompletthetResponse'; +import { Tilstand } from '../types/KompletthetData'; + +export default (tilstand: Tilstand | TilstandResponse): boolean => + tilstand.status.some(({ status }) => status === 'MANGLER'); diff --git a/packages/fakta-inntektsmelding/src/util/utils.ts b/packages/fakta-inntektsmelding/src/util/utils.ts new file mode 100644 index 0000000000..4851613c31 --- /dev/null +++ b/packages/fakta-inntektsmelding/src/util/utils.ts @@ -0,0 +1,38 @@ +import { initializeDate } from '@fpsak-frontend/utils'; +import Aksjonspunkt from '../types/Aksjonspunkt'; +import { Kode, TilstandBeriket } from '../types/KompletthetData'; +import Status from '../types/TilstandStatus'; + +// eslint-disable-next-line import/prefer-default-export +export const finnAktivtAksjonspunkt = (aksjonspunkter: Aksjonspunkt[]): Aksjonspunkt => + aksjonspunkter.find(aksjonspunkt => aksjonspunkt.status.kode === 'OPPR'); + +export const skalVurderes = (tilstand: TilstandBeriket): boolean => + tilstand?.status.some(status => [Status.MANGLER].includes(status.status)) && tilstand?.vurdering?.kode === Kode.TOM; + +export const ikkePaakrevd = (tilstand: TilstandBeriket): boolean => + tilstand?.status.some(status => [Status.IKKE_PÅKREVD].includes(status.status)); + +export const ingenInntektsmeldingMangler = (tilstand: TilstandBeriket): boolean => + !tilstand?.status.some(status => [Status.MANGLER].includes(status.status)); + +export const ingenTilstanderHarMangler = (tilstander: TilstandBeriket[]) => + tilstander.every(ingenInntektsmeldingMangler); + +export const finnTilstanderSomVurderes = (tilstander: TilstandBeriket[]): TilstandBeriket[] => + tilstander.filter(skalVurderes); + +export const finnTilstanderSomRedigeres = (tilstander: TilstandBeriket[]): TilstandBeriket[] => + tilstander.filter(tilstand => tilstand.redigeringsmodus); + +export const sorterSkjæringstidspunkt = (tilstand1: TilstandBeriket, tilstand2: TilstandBeriket): number => { + const date1 = initializeDate(tilstand1?.periode?.fom); + const date2 = initializeDate(tilstand2?.periode?.fom); + if (date1.isBefore(date2)) { + return -1; + } + if (date2.isBefore(date1)) { + return 1; + } + return 0; +}; diff --git a/packages/fakta-inntektsmelding/test/Aksjonspunkt9069.spec.tsx b/packages/fakta-inntektsmelding/test/Aksjonspunkt9069.spec.tsx new file mode 100644 index 0000000000..b626614264 --- /dev/null +++ b/packages/fakta-inntektsmelding/test/Aksjonspunkt9069.spec.tsx @@ -0,0 +1,122 @@ +import { Story, composeStories } from '@storybook/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import React from 'react'; +import { manglerInntektsmelding } from '../mock/mockedKompletthetsdata'; +import * as stories from '../src/stories/MainComponent.stories'; +import MainComponent from '../src/ui/MainComponent'; + +const server = setupServer( + rest.get('http://localhost/tilstand', (req, res, ctx) => res(ctx.json(manglerInntektsmelding))), +); + +describe('9069 - Mangler inntektsmelding', () => { + beforeAll(() => { + server.listen(); + }); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + + const { Mangler9069 } = composeStories(stories) as { + [key: string]: Story>; + }; + + test('Viser ikke knapp for å sende inn når beslutning ikke er valgt', async () => { + // ARRANGE + render(); + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ASSERT + expect(screen.getByLabelText(/Nei, send purring med varsel om avslag/i)).toBeDefined(); + expect(screen.queryByRole('button', { name: /Fortsett uten inntektsmelding/i })).toBeNull(); + expect(screen.queryByRole('button', { name: /Send purring med varsel om avslag/i })).toBeNull(); + }); + + test('Viser riktig knapp når purring er valgt', async () => { + // ARRANGE + render(); + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ACT + await userEvent.click(screen.getByLabelText(/Nei, send purring med varsel om avslag/i)); + + // ASSERT + expect(screen.queryByRole('button', { name: /Fortsett uten inntektsmelding/i })).toBeNull(); + expect(screen.getByRole('button', { name: /Send purring med varsel om avslag/i })).toBeDefined(); + }); + + test('Må skrive begrunnelse når man har valgt A-inntekt', async () => { + // ARRANGE + render(); + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ACT + await userEvent.click(screen.getByText(/ja, bruk a-inntekt for sauefabrikk \(2\) og sauefabrikk \(1\)/i)); + await userEvent.click(screen.getByRole('button', { name: /Fortsett uten inntektsmelding/i })); + + // ASSERT + expect(screen.getByText('Du må fylle inn en verdi')).toBeDefined(); + }); + + test('Kan sende purring med varsel om avslag', async () => { + // ARRANGE + const onClickSpy = jest.fn(); + const data = { onFinished: onClickSpy }; + // eslint-disable-next-line react/jsx-props-no-spreading + render(); + + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ACT + await userEvent.click(screen.getByText(/ja, bruk a-inntekt for sauefabrikk \(2\) og sauefabrikk \(1\)/i)); + await userEvent.type(screen.getByLabelText(/Begrunnelse/i), 'Inntektsmelding? LOL! Nei takk'); + await userEvent.click(screen.getByRole('button', { name: /Fortsett uten inntektsmelding/i })); + + // ASSERT + expect(onClickSpy).toHaveBeenCalledWith({ + '@type': '9069', + kode: '9069', + begrunnelse: 'Inntektsmelding? LOL! Nei takk', + perioder: [ + { + begrunnelse: 'Inntektsmelding? LOL! Nei takk', + periode: '2022-02-01/2022-02-16', + fortsett: true, + kode: '9069', + }, + ], + }); + }); + + test('Kan submitte begrunnelse når man har valgt A-inntekt', async () => { + // ARRANGE + const onClickSpy = jest.fn(); + const data = { onFinished: onClickSpy }; + // eslint-disable-next-line react/jsx-props-no-spreading + render(); + + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ACT + await userEvent.click(screen.getByText(/ja, bruk a-inntekt for sauefabrikk \(2\) og sauefabrikk \(1\)/i)); + await userEvent.type(screen.getByLabelText(/Begrunnelse/i), 'Inntektsmelding? LOL! Nei takk'); + await userEvent.click(screen.getByRole('button', { name: /Fortsett uten inntektsmelding/i })); + + // ASSERT + expect(onClickSpy).toHaveBeenCalledWith({ + '@type': '9069', + kode: '9069', + begrunnelse: 'Inntektsmelding? LOL! Nei takk', + perioder: [ + { + begrunnelse: 'Inntektsmelding? LOL! Nei takk', + periode: '2022-02-01/2022-02-16', + fortsett: true, + kode: '9069', + }, + ], + }); + }); +}); diff --git a/packages/fakta-inntektsmelding/test/Aksjonspunkt9071.spec.tsx b/packages/fakta-inntektsmelding/test/Aksjonspunkt9071.spec.tsx new file mode 100644 index 0000000000..cba5a65421 --- /dev/null +++ b/packages/fakta-inntektsmelding/test/Aksjonspunkt9071.spec.tsx @@ -0,0 +1,143 @@ +/* eslint-disable react/jsx-props-no-spreading */ +import { Story, composeStories } from '@storybook/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import React from 'react'; +import { alleErMottatt, manglerInntektsmelding } from '../mock/mockedKompletthetsdata'; +import * as stories from '../src/stories/MainComponent.stories'; +import MainComponent from '../src/ui/MainComponent'; + +const server = setupServer(); + +describe('9071 - Mangler inntektsmelding', () => { + beforeAll(() => { + server.listen(); + }); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + + const { Mangler9071, AlleInntektsmeldingerMottatt } = composeStories(stories) as { + [key: string]: Story>; + }; + + test('Viser ikke knapp for å sende inn når beslutning ikke er valgt', async () => { + server.use(rest.get('/tilstand', (req, res, ctx) => res(ctx.json(manglerInntektsmelding)))); + // ARRANGE + render(); + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ASSERT + expect(screen.getByLabelText(/Nei, avslå periode på grunn av manglende inntektsopplysninger/i)).toBeDefined(); + expect(screen.queryByRole('button', { name: /Fortsett uten inntektsmelding/i })).toBeNull(); + expect(screen.queryByRole('button', { name: /Send purring med varsel om avslag/i })).toBeNull(); + }); + + test('Viser riktig knapp når purring er valgt', async () => { + server.use(rest.get('/tilstand', (req, res, ctx) => res(ctx.json(manglerInntektsmelding)))); + // ARRANGE + render(); + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ACT + await userEvent.click(screen.getByLabelText(/Nei, avslå periode på grunn av manglende inntektsopplysninger/i)); + + // ASSERT + expect(screen.queryByRole('button', { name: /Fortsett uten inntektsmelding/i })).toBeNull(); + expect(screen.getByRole('button', { name: /Avslå Periode/i })).toBeDefined(); + }); + + test('Må skrive begrunnelse når man har valgt A-inntekt', async () => { + server.use(rest.get('/tilstand', (req, res, ctx) => res(ctx.json(manglerInntektsmelding)))); + // ARRANGE + render(); + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ACT + await userEvent.click(screen.getByText(/ja, bruk a-inntekt for sauefabrikk \(2\) og sauefabrikk \(1\)/i)); + await userEvent.click(screen.getByRole('button', { name: /Fortsett uten inntektsmelding/i })); + + // ASSERT + expect(screen.getByText('Du må fylle inn en verdi')).toBeDefined(); + }); + + test('Kan sende purring med varsel om avslag', async () => { + server.use(rest.get('/tilstand', (req, res, ctx) => res(ctx.json(manglerInntektsmelding)))); + // ARRANGE + const onClickSpy = jest.fn(); + const data = { onFinished: onClickSpy }; + render(); + + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ACT + await userEvent.click(screen.getByText(/ja, bruk a-inntekt for sauefabrikk \(2\) og sauefabrikk \(1\)/i)); + await userEvent.type(screen.getByLabelText(/Begrunnelse/i), 'Inntektsmelding? LOL! Nei takk'); + await userEvent.click(screen.getByRole('button', { name: /Fortsett uten inntektsmelding/i })); + + // ASSERT + expect(onClickSpy).toHaveBeenCalledWith({ + '@type': '9071', + kode: '9071', + begrunnelse: 'Inntektsmelding? LOL! Nei takk', + perioder: [ + { + begrunnelse: 'Inntektsmelding? LOL! Nei takk', + periode: '2022-02-01/2022-02-16', + fortsett: true, + kode: '9071', + }, + ], + }); + }); + + test('Kan submitte begrunnelse når man har valgt A-inntekt', async () => { + server.use(rest.get('/tilstand', (req, res, ctx) => res(ctx.json(manglerInntektsmelding)))); + // ARRANGE + const onClickSpy = jest.fn(); + const data = { onFinished: onClickSpy }; + render(); + + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ACT + await userEvent.click(screen.getByText(/ja, bruk a-inntekt for sauefabrikk \(2\) og sauefabrikk \(1\)/i)); + await userEvent.type(screen.getByLabelText(/Begrunnelse/i), 'Inntektsmelding? LOL! Nei takk'); + await userEvent.click(screen.getByRole('button', { name: /Fortsett uten inntektsmelding/i })); + + // ASSERT + expect(onClickSpy).toHaveBeenCalledWith({ + '@type': '9071', + kode: '9071', + begrunnelse: 'Inntektsmelding? LOL! Nei takk', + perioder: [ + { + begrunnelse: 'Inntektsmelding? LOL! Nei takk', + periode: '2022-02-01/2022-02-16', + fortsett: true, + kode: '9071', + }, + ], + }); + }); + test('Hvis det tidligere er blitt gjort en vurdering og behandlingen har hoppet tilbake må man kunne løse aksjonspunktet', async () => { + server.use(rest.get('/tilstand', (req, res, ctx) => res(ctx.json(alleErMottatt)))); + // ARRANGE + const onClickSpy = jest.fn(); + const data = { onFinished: onClickSpy }; + render(); + + await waitFor(() => screen.getByText(/Når kan du gå videre uten inntektsmelding?/i)); + + // ACT + await userEvent.click(screen.getByRole('button', { name: /Send inn/i })); + + // ASSERT + expect(onClickSpy).toHaveBeenCalledWith({ + '@type': '9071', + kode: '9071', + perioder: [], + }); + }); +}); diff --git a/packages/fakta-inntektsmelding/webpack/webpack-config.development.js b/packages/fakta-inntektsmelding/webpack/webpack-config.development.js new file mode 100644 index 0000000000..151f65b99d --- /dev/null +++ b/packages/fakta-inntektsmelding/webpack/webpack-config.development.js @@ -0,0 +1,40 @@ +/* eslint-disable import/extensions */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const webpack = require('webpack'); +const path = require('path'); +const { merge } = require('webpack-merge'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const WebpackDevServer = require('webpack-dev-server'); +const commonWebpackConfig = require('./webpack.common.js'); + +const webpackConfig = merge(commonWebpackConfig, { + entry: `${path.resolve(__dirname, '../', 'src')}/dev/app.ts`, + mode: 'development', + devtool: 'eval-cheap-module-source-map', + plugins: [ + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, '../index.html'), + }), + ], +}); + +const port = 8484; +const devServerOptions = { + hot: true, + headers: { + 'Access-Control-Allow-Origin': 'http://localhost:9000', + }, + port, +}; + +const compiler = webpack(webpackConfig); +const devServer = new WebpackDevServer(devServerOptions, compiler); +compiler.close(() => console.info('Compiler closed')); + +devServer.startCallback(error => { + if (error) { + console.error(error); + } else { + console.log(`Listening at port ${port}`); + } +}); diff --git a/packages/fakta-inntektsmelding/webpack/webpack.common.js b/packages/fakta-inntektsmelding/webpack/webpack.common.js new file mode 100644 index 0000000000..ffc2e21ed2 --- /dev/null +++ b/packages/fakta-inntektsmelding/webpack/webpack.common.js @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const process = require('process'); +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const cssExtractLoaderConfig = { + loader: MiniCssExtractPlugin.loader, +}; + +const nodeModules = path.resolve(__dirname, '../node_modules'); +const rootNodeModules = path.resolve(__dirname, '../../../node_modules'); + +module.exports = { + resolve: { + extensions: ['.ts', '.tsx', '.js', '.css'], + }, + module: { + rules: [ + { + test: /\.(ts|js)x?$/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + rootMode: 'upward', + }, + }, + }, + { + test: /\.css$/, + use: [ + cssExtractLoaderConfig, + { + loader: 'css-loader', + options: { + importLoaders: 1, + modules: { + localIdentName: '[name]_[local]_[contenthash:base64:5]', + }, + }, + }, + ], + exclude: [nodeModules, rootNodeModules], + }, + { + test: /\.(less|css)?$/, + use: [ + cssExtractLoaderConfig, + { + loader: 'css-loader', + }, + { + loader: 'less-loader', + options: { + lessOptions: { + modifyVars: { + nodeModulesPath: '~', + coreModulePath: '~', + }, + }, + }, + }, + ], + include: [nodeModules, rootNodeModules], + }, + { + test: /\.(jpg|png|svg)$/, + loader: 'file-loader', + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: 'styles.css', + }), + ], +}; diff --git a/packages/fakta-institusjon/package.json b/packages/fakta-institusjon/package.json index c1109856d0..156d5ed4ea 100644 --- a/packages/fakta-institusjon/package.json +++ b/packages/fakta-institusjon/package.json @@ -7,8 +7,8 @@ "dependencies": { "@fpsak-frontend/form": "*", "@k9-sak-web/types": "*", - "@navikt/ft-plattform-komponenter": "2.3.10", - "@navikt/k9-fe-form-utils": "1.0.16", + "@navikt/ft-plattform-komponenter": "2.3.14", + "@navikt/k9-fe-form-utils": "1.0.17", "@navikt/k9-fe-period-utils": "1.0.12", "formik": "2.4.5", "yup": "0.32.11" diff --git a/packages/fakta-institusjon/src/InstitusjonForm.tsx b/packages/fakta-institusjon/src/InstitusjonForm.tsx index 8630c2461b..6ad3b69085 100644 --- a/packages/fakta-institusjon/src/InstitusjonForm.tsx +++ b/packages/fakta-institusjon/src/InstitusjonForm.tsx @@ -1,4 +1,3 @@ -import { Period } from '@navikt/k9-fe-period-utils'; import { Box, Margin, DetailView, LabelledContent, Form } from '@navikt/ft-plattform-komponenter'; import { TextAreaFormik } from '@fpsak-frontend/form'; import { Calender } from '@navikt/ds-icons'; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/cypress/.eslintrc.json" "b/packages/fakta-medisinsk-vilk\303\245r/cypress/.eslintrc.json" new file mode 100644 index 0000000000..7eba2f7399 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/cypress/.eslintrc.json" @@ -0,0 +1,5 @@ +{ + "extends": [ + "plugin:cypress/recommended" + ] +} \ No newline at end of file diff --git "a/packages/fakta-medisinsk-vilk\303\245r/cypress/e2e/Sykdom.cy.js" "b/packages/fakta-medisinsk-vilk\303\245r/cypress/e2e/Sykdom.cy.js" new file mode 100644 index 0000000000..9b15e2d79e --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/cypress/e2e/Sykdom.cy.js" @@ -0,0 +1,52 @@ +describe('Sykdom', () => { + before(() => { + cy.visit('/'); + }); + it('skal kunne håndtere dokumentasjon av sykdom', () => { + cy.contains('Ja, legeerklæring fra sykehus/spesialisthelsetjenesten').click(); + cy.findByLabelText(/Hvilken dato er dokumentet datert?/).type('101021'); + cy.contains('Bekreft').click(); + }); + it('skal kunne legge inn innleggelsesperioder', () => { + cy.contains('Dokumentasjon av sykdom').click(); + cy.contains('Rediger liste').click(); + cy.contains('Legg til innleggelsesperiode').click(); + cy.get('input[id="innleggelsesperioder[3].fom"]').type('010123'); + cy.get('input[id="innleggelsesperioder[3].tom"]').type('300123'); + cy.get('dialog').contains('Bekreft').click(); + cy.contains('Fortsett').click(); + }); + it('skal kunne håndtere tilsyn og pleie', () => { + cy.get('[type="checkbox"]').first().check({ force: true }); + cy.get('[name="vurderingAvKontinuerligTilsynOgPleie"]').type('test'); + cy.get('input[id="harBehovForKontinuerligTilsynOgPleieYES"]').check({ force: true }); + cy.get('input[id="perioder[0].tom"]').clear(); + cy.get('input[id="perioder[0].tom"]').type('020322'); + cy.get('input[id="perioder[0].fom"]').click(); + cy.contains('Du har valgt en dato som er utenfor gyldig periode.').should('exist'); + cy.get('input[id="perioder[0].tom"]').clear(); + cy.get('input[id="perioder[0].tom"]').type('280222'); + cy.get('input[id="perioder[0].fom"]').click(); + cy.contains('Du har valgt en dato som er utenfor gyldig periode.').should('not.exist'); + cy.contains( + 'Du har ikke vurdert alle periodene som må vurderes. Resterende perioder vurderer du etter at du har lagret denne.', + ).should('exist'); + cy.get('input[id="perioder[0].tom"]').clear(); + cy.get('input[id="perioder[0].tom"]').type('010322'); + cy.get('input[id="perioder[0].fom"]').click(); + cy.contains( + 'Du har ikke vurdert alle periodene som må vurderes. Resterende perioder vurderer du etter at du har lagret denne.', + ).should('not.exist'); + cy.contains('Bekreft').click(); + cy.get('dialog').find('button').contains('Bekreft').click(); + cy.contains('Eventuelle endringer er registrert').click(); + }); + it('skal kunne håndtere to omsorgspersoner', () => { + cy.get('[type="checkbox"]').first().check({ force: true }); + cy.get('[name="vurderingAvToOmsorgspersoner"]').type('test'); + cy.get('input[id="harBehovForToOmsorgspersonerYES"]').check({ force: true }); + cy.contains('Bekreft').click(); + cy.get('dialog').find('button').contains('Bekreft').click(); + cy.contains('Sykdom er ferdig vurdert og du kan gå videre i behandlingen.').should('exist'); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/index.html" "b/packages/fakta-medisinsk-vilk\303\245r/index.html" new file mode 100644 index 0000000000..b36ad62b9d --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/index.html" @@ -0,0 +1,45 @@ + + + Sykdom + + + + + +
    + + + diff --git "a/packages/fakta-medisinsk-vilk\303\245r/index.ts" "b/packages/fakta-medisinsk-vilk\303\245r/index.ts" new file mode 100644 index 0000000000..d2de580259 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/index.ts" @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as MedisinskVilkår } from './src/ui/MainComponent'; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/api-mock-livets-sluttfase.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/api-mock-livets-sluttfase.ts" new file mode 100644 index 0000000000..2125379bd8 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/api-mock-livets-sluttfase.ts" @@ -0,0 +1,162 @@ +import express from 'express'; +import cors from 'cors'; +import mockedDokumentliste from './mocked-data/mockedDokumentliste'; +import mockedDokumentoversikt from './mocked-data/mockedDokumentoversikt'; +import { createLivetsSluttfaseVurdering } from './apiUtils'; +import createStrukturertDokument from './mocked-data/createStrukturertDokument'; +import { Dokumenttype } from '../src/types/Dokument'; +import createMockedVurderingselementLinks from './mocked-data/createMockedVurderingselementLinks'; +import mockedNyeDokumenterList from './mocked-data/mockedNyeDokumenter'; +import livetsSluttfaseVurderingsoversiktMock from './mocked-data/mockedLivetsSluttfaseVurderingsoversikt'; +import livetsSluttfaseVurderingerMock from './mocked-data/mockedLivetsSluttfaseVurderinger'; + +const app = express(); + +app.use(express.json()); + +app.use( + cors({ + origin: 'http://localhost:8081', + }), +); + +let mockedNyeDokumenter = [...mockedNyeDokumenterList]; + +app.use('/mock/status', (req, res) => { + const harUklassifiserteDokumenter = mockedDokumentoversikt.dokumenter.some( + ({ type }) => type === Dokumenttype.UKLASSIFISERT, + ); + const manglerGodkjentLegeerklæring = + mockedDokumentoversikt.dokumenter.some(({ type }) => type === Dokumenttype.LEGEERKLÆRING) === false; + const manglerVurderingAvILivetsSluttfase = + livetsSluttfaseVurderingsoversiktMock.resterendeVurderingsperioder.length > 0; + const nyttDokumentHarIkkekontrollertEksisterendeVurderinger = mockedNyeDokumenter.length > 0; + const harDataSomIkkeHarBlittTattMedIBehandling = true; + + res.send({ + kanLøseAksjonspunkt: + !harUklassifiserteDokumenter && + !manglerGodkjentLegeerklæring && + !manglerVurderingAvILivetsSluttfase && + !nyttDokumentHarIkkekontrollertEksisterendeVurderinger, + harUklassifiserteDokumenter, + manglerGodkjentLegeerklæring, + harDataSomIkkeHarBlittTattMedIBehandling, + nyttDokumentHarIkkekontrollertEksisterendeVurderinger, + }); +}); + +app.use('/mock/vurdering', (req, res) => { + const vurderingId = req.query.sykdomVurderingId; + const alleVurderinger = [...livetsSluttfaseVurderingerMock]; + const vurdering = alleVurderinger.find(({ id }) => id === vurderingId); + res.send(vurdering); +}); + +app.use('/mock/opprett-vurdering', (req, res) => { + if (req.body.dryRun === true) { + res.send({ + perioderMedEndringer: [ + { + periode: { + fom: '2024-01-01', + tom: '2024-01-10', + }, + endrerVurderingSammeBehandling: true, + endrerAnnenVurdering: false, + }, + ], + }); + } else { + createLivetsSluttfaseVurdering(req.body); + res.send(); + } +}); + +app.use('/mock/endre-vurdering', (req, res) => { + if (req.body.dryRun === true) { + res.send({ + perioderMedEndringer: [ + { + periode: { + fom: '2024-01-01', + tom: '2024-01-10', + }, + endrerVurderingSammeBehandling: true, + endrerAnnenVurdering: false, + }, + ], + }); + } else { + const { id } = req.body; + const { perioder } = req.body; + livetsSluttfaseVurderingsoversiktMock.vurderingselementer = + livetsSluttfaseVurderingsoversiktMock.vurderingselementer.filter(element => id !== element.id); + perioder.forEach(periode => { + livetsSluttfaseVurderingsoversiktMock.vurderingselementer.unshift({ + id, + periode, + resultat: req.body.resultat, + gjelderForSøker: true, + gjelderForAnnenPart: false, + links: createMockedVurderingselementLinks(id), + endretIDenneBehandlingen: true, + }); + }); + + const index = livetsSluttfaseVurderingerMock.findIndex(element => element.id === id); + if (livetsSluttfaseVurderingerMock[index]) { + livetsSluttfaseVurderingerMock[index].versjoner.unshift({ + perioder, + resultat: req.body.resultat, + dokumenter: mockedDokumentliste, + tekst: req.body.tekst, + endretAv: req.body.endretAv, + endretTidspunkt: req.body.endretTidspunkt, + }); + } + res.send(); + } +}); + +app.use('/mock/livets-sluttfase/vurderingsoversikt', (req, res) => { + const harGyldigSignatur = mockedDokumentoversikt.dokumenter.some(({ type }) => type === Dokumenttype.LEGEERKLÆRING); + res.send({ + ...livetsSluttfaseVurderingsoversiktMock, + harGyldigSignatur, + resterendeVurderingsperioder: !harGyldigSignatur + ? [] + : livetsSluttfaseVurderingsoversiktMock.resterendeVurderingsperioder, + }); +}); + +app.use('/mock/dokumentoversikt', (req, res) => { + res.send(mockedDokumentoversikt); +}); + +app.use('/mock/endre-dokument', (req, res) => { + createStrukturertDokument(req.body); + res.send(mockedDokumentoversikt); +}); + +app.use('/mock/data-til-vurdering', (req, res) => { + res.send(mockedDokumentliste); +}); + +app.get('/mock/nye-dokumenter', (req, res) => { + res.send(mockedNyeDokumenter); +}); + +app.post('/mock/nye-dokumenter', (req, res) => { + mockedNyeDokumenter = []; + res.send({}); +}); + +app.get('/', (req, res) => { + res.status(200).send('ok'); +}); + +const port = 8082; +app.listen(port, () => { + console.log('API-mock listening on port', port); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/apiUtils.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/apiUtils.ts" new file mode 100644 index 0000000000..173817ed87 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/apiUtils.ts" @@ -0,0 +1,111 @@ +import tilsynsbehovVurderingerMock from './mocked-data/mockedTilsynsbehovVurderinger'; +import tilsynsbehovVurderingsoversiktMock from './mocked-data/mockedTilsynsbehovVurderingsoversikt'; +import mockedToOmsorgspersonerVurderingsoversikt from './mocked-data/mockedToOmsorgspersonerVurderingsoversikt'; +import toOmsorgspersonerVurderingerMock from './mocked-data/mockedToOmsorgspersonerVurderinger'; +import createMockedVurderingselementLinks from './mocked-data/createMockedVurderingselementLinks'; +import NyVurderingsversjon from '../src/types/NyVurderingsversjon'; +import mockedDokumentliste from './mocked-data/mockedDokumentliste'; +import livetsSluttfaseVurderingsoversiktMock from './mocked-data/mockedLivetsSluttfaseVurderingsoversikt'; +import livetsSluttfaseVurderingerMock from './mocked-data/mockedLivetsSluttfaseVurderinger'; + +export const createKontinuerligTilsynVurdering = (requestBody: NyVurderingsversjon) => { + const nyVurderingId = tilsynsbehovVurderingsoversiktMock.vurderingselementer.length + 1; + const { type, perioder, resultat, tekst } = requestBody; + + tilsynsbehovVurderingsoversiktMock.vurderingselementer.push({ + id: `${nyVurderingId}`, + periode: perioder[0], + resultat, + gjelderForSøker: true, + gjelderForAnnenPart: false, + links: createMockedVurderingselementLinks(nyVurderingId), + endretIDenneBehandlingen: true, + erInnleggelsesperiode: false, + }); + tilsynsbehovVurderingsoversiktMock.resterendeVurderingsperioder = []; + tilsynsbehovVurderingerMock.push({ + id: `${nyVurderingId}`, + type, + versjoner: [ + { + perioder, + resultat, + dokumenter: mockedDokumentliste, + tekst, + }, + ], + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, + erInnleggelsesperiode: false, + }); +}; + +export const createToOmsorgspersonerVurdering = (requestBody: NyVurderingsversjon) => { + const nyVurderingId = mockedToOmsorgspersonerVurderingsoversikt.vurderingselementer.length + 11; + const { type, perioder, resultat, tekst } = requestBody; + + mockedToOmsorgspersonerVurderingsoversikt.vurderingselementer.push({ + id: `${nyVurderingId}`, + periode: perioder[0], + resultat, + gjelderForSøker: true, + gjelderForAnnenPart: false, + links: createMockedVurderingselementLinks(nyVurderingId), + endretIDenneBehandlingen: true, + erInnleggelsesperiode: false, + }); + mockedToOmsorgspersonerVurderingsoversikt.resterendeVurderingsperioder = []; + toOmsorgspersonerVurderingerMock.push({ + id: `${nyVurderingId}`, + type, + versjoner: [ + { + perioder, + resultat, + dokumenter: mockedDokumentliste, + tekst, + }, + ], + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, + erInnleggelsesperiode: false, + }); +}; + +export const createLivetsSluttfaseVurdering = (requestBody: NyVurderingsversjon) => { + const nyVurderingId = livetsSluttfaseVurderingsoversiktMock.vurderingselementer.length + 1; + const { type, perioder, resultat, tekst } = requestBody; + + livetsSluttfaseVurderingsoversiktMock.vurderingselementer.push({ + id: `${nyVurderingId}`, + periode: perioder[0], + resultat, + gjelderForSøker: true, + gjelderForAnnenPart: false, + links: createMockedVurderingselementLinks(nyVurderingId), + endretIDenneBehandlingen: true, + }); + livetsSluttfaseVurderingsoversiktMock.resterendeVurderingsperioder = []; + livetsSluttfaseVurderingerMock.push({ + id: `${nyVurderingId}`, + type, + versjoner: [ + { + perioder, + resultat, + dokumenter: mockedDokumentliste, + tekst, + endretAv: 'Z199493', + endretTidspunkt: '2021-10-20T13:23:44.995', + }, + ], + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, + }); +}; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/handlers.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/handlers.ts" new file mode 100644 index 0000000000..ca76cf742d --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/handlers.ts" @@ -0,0 +1,220 @@ +/* eslint-disable import/prefer-default-export */ +import express from 'express'; +import cors from 'cors'; +import { rest } from 'msw'; +import mockedTilsynsbehovVurderingsoversikt from './mocked-data/mockedTilsynsbehovVurderingsoversikt'; +import mockedToOmsorgspersonerVurderingsoversikt from './mocked-data/mockedToOmsorgspersonerVurderingsoversikt'; +import mockedToOmsorgspersonerVurderinger from './mocked-data/mockedToOmsorgspersonerVurderinger'; +import mockedTilsynsbehovVurderinger from './mocked-data/mockedTilsynsbehovVurderinger'; +import mockedDokumentliste from './mocked-data/mockedDokumentliste'; +import mockedDokumentoversikt from './mocked-data/mockedDokumentoversikt'; +import { createKontinuerligTilsynVurdering, createToOmsorgspersonerVurdering } from './apiUtils'; +import Vurderingstype from '../src/types/Vurderingstype'; +import mockedDiagnosekoderesponse from './mocked-data/mockedDiagnosekodeResponse'; +import createStrukturertDokument from './mocked-data/createStrukturertDokument'; +import mockedInnleggelsesperioder from './mocked-data/mockedInnleggelsesperioder'; +import { Dokumenttype } from '../src/types/Dokument'; +import createMockedVurderingselementLinks from './mocked-data/createMockedVurderingselementLinks'; +import mockedNyeDokumenterList from './mocked-data/mockedNyeDokumenter'; + +let mockedNyeDokumenter = [...mockedNyeDokumenterList]; + +export const handlers = [ + rest.get('http://localhost:8082/mock/status', (req, res, ctx) => { + const harUklassifiserteDokumenter = mockedDokumentoversikt.dokumenter.some( + ({ type }) => type === Dokumenttype.UKLASSIFISERT, + ); + const manglerDiagnosekode = + !mockedDiagnosekoderesponse || + !mockedDiagnosekoderesponse.diagnosekoder || + mockedDiagnosekoderesponse.diagnosekoder.length === 0; + const manglerGodkjentLegeerklæring = + mockedDokumentoversikt.dokumenter.some(({ type }) => type === Dokumenttype.LEGEERKLÆRING) === false; + const manglerVurderingAvKontinuerligTilsynOgPleie = + mockedTilsynsbehovVurderingsoversikt.resterendeVurderingsperioder.length > 0; + const manglerVurderingAvToOmsorgspersoner = + mockedToOmsorgspersonerVurderingsoversikt.resterendeVurderingsperioder.length > 0; + const nyttDokumentHarIkkekontrollertEksisterendeVurderinger = mockedNyeDokumenter.length > 0; + const harDataSomIkkeHarBlittTattMedIBehandling = true; + + return res( + ctx.status(200), + ctx.json({ + kanLøseAksjonspunkt: + !harUklassifiserteDokumenter && + !manglerDiagnosekode && + !manglerGodkjentLegeerklæring && + !manglerVurderingAvKontinuerligTilsynOgPleie && + !manglerVurderingAvToOmsorgspersoner && + !nyttDokumentHarIkkekontrollertEksisterendeVurderinger, + harUklassifiserteDokumenter, + manglerDiagnosekode, + manglerGodkjentLegeerklæring, + manglerVurderingAvKontinuerligTilsynOgPleie, + manglerVurderingAvToOmsorgspersoner, + harDataSomIkkeHarBlittTattMedIBehandling, + nyttDokumentHarIkkekontrollertEksisterendeVurderinger, + }), + ); + }), + + rest.get('http://localhost:8082/mock/vurdering', (req, res, ctx) => { + const vurderingId = req.url.searchParams.get('sykdomVurderingId'); + const alleVurderinger = [...mockedTilsynsbehovVurderinger, ...mockedToOmsorgspersonerVurderinger]; + const vurdering = alleVurderinger.find(({ id }) => id === vurderingId); + return res(ctx.status(200), ctx.json(vurdering)); + }), + + rest.post('http://localhost:8082/mock/opprett-vurdering', async (req, res, ctx) => { + const body = await req.json(); + if (body.dryRun === true) { + return res( + ctx.status(200), + ctx.json({ + perioderMedEndringer: [ + { + periode: { + fom: '2024-01-01', + tom: '2024-01-10', + }, + endrerVurderingSammeBehandling: true, + endrerAnnenVurdering: false, + }, + ], + }), + ); + } + if (body.type === Vurderingstype.KONTINUERLIG_TILSYN_OG_PLEIE) { + createKontinuerligTilsynVurdering(body); + } else { + createToOmsorgspersonerVurdering(body); + } + return res(ctx.status(201)); + }), + + rest.post('http://localhost:8082/mock/endre-vurdering', async (req, res, ctx) => { + const body = await req.json(); + if (body.dryRun === true) { + return res( + ctx.status(201), + ctx.json({ + perioderMedEndringer: [ + { + periode: { + fom: '2024-01-01', + tom: '2024-01-10', + }, + endrerVurderingSammeBehandling: true, + endrerAnnenVurdering: false, + }, + ], + }), + ); + } + const { id } = body; + const { perioder } = body; + mockedTilsynsbehovVurderingsoversikt.vurderingselementer = + mockedTilsynsbehovVurderingsoversikt.vurderingselementer.filter(element => id !== element.id); + perioder.forEach(periode => { + mockedTilsynsbehovVurderingsoversikt.vurderingselementer.unshift({ + id, + periode, + resultat: body.resultat, + gjelderForSøker: true, + gjelderForAnnenPart: false, + links: createMockedVurderingselementLinks(id), + endretIDenneBehandlingen: true, + erInnleggelsesperiode: false, + }); + }); + + const index = mockedTilsynsbehovVurderinger.findIndex(element => element.id === id); + if (mockedTilsynsbehovVurderinger[index]) { + mockedTilsynsbehovVurderinger[index].versjoner.unshift({ + perioder, + resultat: body.resultat, + dokumenter: mockedDokumentliste, + tekst: body.tekst, + endretAv: body.endretAv, + endretTidspunkt: body.endretTidspunkt, + }); + } + return res(ctx.status(201)); + }), + + rest.get('http://localhost:8082/mock/kontinuerlig-tilsyn-og-pleie/vurderingsoversikt', (req, res, ctx) => { + const harGyldigSignatur = mockedDokumentoversikt.dokumenter.some(({ type }) => type === Dokumenttype.LEGEERKLÆRING); + return res( + ctx.status(200), + ctx.json({ + ...mockedTilsynsbehovVurderingsoversikt, + harGyldigSignatur, + resterendeVurderingsperioder: !harGyldigSignatur + ? [] + : mockedTilsynsbehovVurderingsoversikt.resterendeVurderingsperioder, + }), + ); + }), + + rest.get('http://localhost:8082/mock/to-omsorgspersoner/vurderingsoversikt', (req, res, ctx) => { + const harGyldigSignatur = mockedDokumentoversikt.dokumenter.some(({ type }) => type === Dokumenttype.LEGEERKLÆRING); + return res( + ctx.status(200), + ctx.json({ + ...mockedToOmsorgspersonerVurderingsoversikt, + harGyldigSignatur, + resterendeVurderingsperioder: !harGyldigSignatur + ? [] + : mockedToOmsorgspersonerVurderingsoversikt.resterendeVurderingsperioder, + }), + ); + }), + + rest.get('http://localhost:8082/mock/dokumentoversikt', (req, res, ctx) => + res(ctx.status(200), ctx.json(mockedDokumentoversikt)), + ), + + rest.post('http://localhost:8082/mock/endre-dokument', async (req, res, ctx) => { + const body = await req.json(); + createStrukturertDokument(body); + return res(ctx.status(201), ctx.json(mockedDokumentoversikt)); + }), + + rest.get('http://localhost:8082/mock/data-til-vurdering', (req, res, ctx) => + res(ctx.status(200), ctx.json(mockedDokumentliste)), + ), + + rest.get('http://localhost:8082/mock/diagnosekoder', (req, res, ctx) => + res(ctx.status(200), ctx.json(mockedDiagnosekoderesponse)), + ), + + rest.post('http://localhost:8082/mock/endre-diagnosekoder', async (req, res, ctx) => { + const body = await req.json(); + mockedDiagnosekoderesponse.diagnosekoder = body.diagnosekoder || []; + return res(ctx.status(201), ctx.json({})); + }), + + rest.get('http://localhost:8082/mock/innleggelsesperioder', (req, res, ctx) => + res(ctx.status(200), ctx.json(mockedInnleggelsesperioder)), + ), + + rest.post('http://localhost:8082/mock/endre-innleggelsesperioder', async (req, res, ctx) => { + const body = await req.json(); + if (body.dryRun === true) { + return res(ctx.status(200), ctx.json({ førerTilRevurdering: true })); + } + mockedInnleggelsesperioder.perioder = body.perioder || []; + return res(ctx.status(200), ctx.json({})); + }), + + rest.get('http://localhost:8082/mock/nye-dokumenter', (req, res, ctx) => + res(ctx.status(200), ctx.json(mockedNyeDokumenter)), + ), + + rest.post('http://localhost:8082/mock/nye-dokumenter', (req, res, ctx) => { + mockedNyeDokumenter = []; + return res(ctx.status(201), ctx.json({})); + }), + + rest.get('/', (req, res, ctx) => res(ctx.status(200))), +]; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/createMockedDokumentelementLinks.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/createMockedDokumentelementLinks.ts" new file mode 100644 index 0000000000..8cc2624f3c --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/createMockedDokumentelementLinks.ts" @@ -0,0 +1,19 @@ +import Link from '../../src/types/Link'; +import LinkRel from '../../src/constants/LinkRel'; + +const createMockedDokumentelementLinks = (id: string): Link[] => [ + { + rel: LinkRel.ENDRE_DOKUMENT, + type: 'POST', + href: `http://localhost:8082/mock/endre-dokument?dokumentId=${id}`, + versjon: null, + }, + { + rel: LinkRel.DOKUMENT_INNHOLD, + type: 'GET', + href: `#`, + versjon: null, + }, +]; + +export default createMockedDokumentelementLinks; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/createMockedVurderingselementLinks.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/createMockedVurderingselementLinks.ts" new file mode 100644 index 0000000000..1c225d3cc2 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/createMockedVurderingselementLinks.ts" @@ -0,0 +1,24 @@ +import Link from '../../src/types/Link'; +import LinkRel from '../../src/constants/LinkRel'; + +function createMockedVurderingselementLinks(id): Link[] { + return [ + { + rel: LinkRel.HENT_VURDERING, + type: 'GET', + href: `http://localhost:8082/mock/vurdering?sykdomVurderingId=${id}`, + versjon: null, + }, + { + rel: LinkRel.ENDRE_VURDERING, + type: 'POST', + href: `http://localhost:8082/mock/endre-vurdering?sykdomVurderingId=${id}`, + versjon: null, + requestPayload: { + behandlingUuid: '123', + }, + }, + ]; +} + +export default createMockedVurderingselementLinks; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/createStrukturertDokument.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/createStrukturertDokument.ts" new file mode 100644 index 0000000000..2f4be55414 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/createStrukturertDokument.ts" @@ -0,0 +1,9 @@ +import mockedDokumentoversikt from './mockedDokumentoversikt'; +import Dokument from '../../src/types/Dokument'; + +const createStrukturertDokument = (dokument: Dokument) => { + const index = mockedDokumentoversikt.dokumenter.findIndex(({ id }) => dokument.id === id); + mockedDokumentoversikt.dokumenter[index] = dokument; +}; + +export default createStrukturertDokument; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDiagnosekodeResponse.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDiagnosekodeResponse.ts" new file mode 100644 index 0000000000..290ae9fcf7 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDiagnosekodeResponse.ts" @@ -0,0 +1,14 @@ +import LinkRel from '../../src/constants/LinkRel'; + +export default { + diagnosekoder: ['A001', 'B04'], + links: [ + { + rel: LinkRel.ENDRE_DIAGNOSEKODER, + type: 'POST', + href: 'http://localhost:8082/mock/endre-diagnosekoder', + behandlingUuid: 'HER_ER_BEHANDLINGSID', + versjon: null, + }, + ], +}; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDiagnosekodeSearchResponse.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDiagnosekodeSearchResponse.ts" new file mode 100644 index 0000000000..cdae7f45d3 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDiagnosekodeSearchResponse.ts" @@ -0,0 +1,10 @@ +export default [ + { kode: 'T4n', beskrivelse: 'Forgiftning med terapeutisk legemiddel eller biologisk substans' }, + { kode: 'A371', beskrivelse: 'Kikhoste som skyldes Bordetella parapertussis' }, + { kode: 'B04', beskrivelse: 'Apekopper' }, + { kode: 'B531', beskrivelse: 'Apeplasmodiamalaria' }, + { kode: 'F55', beskrivelse: 'Misbruk av ikke-avhengighetsskapende stoffer' }, + { kode: 'F558', beskrivelse: 'Misbruk av andre ikke-avhengighetsskapende stoffer' }, + { kode: 'F559', beskrivelse: 'Misbruk av uspesifisert ikke-avhengighetsskapende stoff' }, + { kode: 'K670', beskrivelse: 'Klamydiaperitonitt' }, +]; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDokumentliste.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDokumentliste.ts" new file mode 100644 index 0000000000..6a01ef31a9 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDokumentliste.ts" @@ -0,0 +1,35 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import { Dokumenttype } from '../../src/types/Dokument'; +import createMockedDokumentelementLinks from './createMockedDokumentelementLinks'; + +dayjs.extend(utc); + +export default [ + { + id: '1', + type: Dokumenttype.LEGEERKLÆRING, + datert: dayjs('2020-01-16').utc(true).toISOString(), + navn: 'Foobar-lala.pdf', + benyttet: true, + annenPartErKilde: false, + fremhevet: true, + behandlet: true, + links: createMockedDokumentelementLinks('1'), + mottattDato: '2021-03-05', + mottattTidspunkt: '2021-03-05T10:23:13.309266', + }, + { + id: '2', + type: Dokumenttype.LEGEERKLÆRING, + datert: dayjs('2020-01-01').utc(true).toISOString(), + navn: 'Foobar-haha.pdf', + benyttet: true, + annenPartErKilde: true, + fremhevet: true, + behandlet: true, + links: createMockedDokumentelementLinks('2'), + mottattDato: '2021-03-06', + mottattTidspunkt: '2021-03-06T10:23:13.309267', + }, +]; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDokumentoversikt.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDokumentoversikt.ts" new file mode 100644 index 0000000000..efbb448d0f --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedDokumentoversikt.ts" @@ -0,0 +1,60 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import { Dokumenttype } from '../../src/types/Dokument'; +import createMockedDokumentelementLinks from './createMockedDokumentelementLinks'; + +dayjs.extend(utc); + +const mockedDokumentoversikt = { + dokumenter: [ + { + id: '2', + navn: 'Dokument 2', + type: Dokumenttype.ANDRE_MEDISINSKE_OPPLYSNINGER, + datert: dayjs('2022-02-10').utc(true).format('YYYY-MM-DD'), + links: createMockedDokumentelementLinks('2'), + benyttet: true, + annenPartErKilde: false, + fremhevet: false, + behandlet: true, + mottattDato: '2021-03-05', + mottattTidspunkt: '2021-03-05T10:23:13.309267', + duplikater: ['3'], + duplikatAvId: null, + bruktTilMinstEnVurdering: false, + }, + { + id: '4', + navn: 'Dokument 4', + type: Dokumenttype.MANGLER_MEDISINSKE_OPPLYSNINGER, + datert: dayjs('2022-02-11').utc(true).format('YYYY-MM-DD'), + links: createMockedDokumentelementLinks('4'), + benyttet: true, + annenPartErKilde: false, + fremhevet: false, + behandlet: true, + mottattDato: '2021-03-05', + mottattTidspunkt: '2021-03-05T10:23:13.309267', + duplikater: [], + duplikatAvId: null, + bruktTilMinstEnVurdering: false, + }, + { + id: '1', + navn: 'Dokument 1', + type: Dokumenttype.UKLASSIFISERT, + datert: dayjs('2022-02-12').utc(true).format('YYYY-MM-DD'), + links: createMockedDokumentelementLinks('4'), + benyttet: true, + annenPartErKilde: false, + fremhevet: false, + behandlet: false, + mottattDato: '2021-03-05', + mottattTidspunkt: '2021-03-05T10:23:13.309267', + duplikater: [], + duplikatAvId: null, + bruktTilMinstEnVurdering: false, + }, + ], +}; +export default mockedDokumentoversikt; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedInnleggelsesperioder.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedInnleggelsesperioder.ts" new file mode 100644 index 0000000000..59696708c5 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedInnleggelsesperioder.ts" @@ -0,0 +1,20 @@ +import LinkRel from '../../src/constants/LinkRel'; + +export default { + perioder: [ + { fom: '2021-01-01', tom: '2021-01-15' }, + { fom: '2021-01-16', tom: '2021-01-20' }, + { fom: '2021-01-21', tom: '2021-01-30' }, + ], + links: [ + { + rel: LinkRel.ENDRE_INNLEGGELSESPERIODER, + type: 'POST', + href: 'http://localhost:8082/mock/endre-innleggelsesperioder', + requestPayload: { + behandlingUuid: 'HER_ER_BEHANDLINGSID', + versjon: null, + }, + }, + ], +}; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedLivetsSluttfaseVurderinger.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedLivetsSluttfaseVurderinger.ts" new file mode 100644 index 0000000000..45ecb76b95 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedLivetsSluttfaseVurderinger.ts" @@ -0,0 +1,25 @@ +import Vurderingsresultat from '../../src/types/Vurderingsresultat'; +import mockedDokumentliste from './mockedDokumentliste'; + +const livetsSluttfaseVurderingerMock = [ + { + id: '1', + type: 'LIVETS_SLUTTFASE', + versjoner: [ + { + perioder: [{ fom: '2022-02-01', tom: '2022-02-15' } as any], + resultat: Vurderingsresultat.OPPFYLT, + dokumenter: mockedDokumentliste, + tekst: 'Fordi her er det behov', + endretAv: 'Z199493', + endretTidspunkt: '2021-10-20T13:23:44.995', + }, + ], + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, + }, +]; + +export default livetsSluttfaseVurderingerMock; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedLivetsSluttfaseVurderingsoversikt.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedLivetsSluttfaseVurderingsoversikt.ts" new file mode 100644 index 0000000000..b86c444c76 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedLivetsSluttfaseVurderingsoversikt.ts" @@ -0,0 +1,15 @@ +import Vurderingsresultat from '../../src/types/Vurderingsresultat'; +import mockedVurderingsoversiktLinks from './mockedVurderingsoversiktLinks'; +import createMockedVurderingselementLinks from './createMockedVurderingselementLinks'; + +const livetsSluttfaseVurderingsoversiktMock = { + vurderingselementer: [], + resterendeVurderingsperioder: [{ fom: '2022-02-16', tom: '2022-03-01' } as any], + resterendeValgfrieVurderingsperioder: [], + søknadsperioderTilBehandling: [], + perioderSomKanVurderes: [{ fom: '2022-02-01', tom: '2022-03-01' } as any], + links: mockedVurderingsoversiktLinks, + pleietrengendesFødselsdato: '2004-02-28', +}; + +export default livetsSluttfaseVurderingsoversiktMock; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedNyeDokumenter.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedNyeDokumenter.ts" new file mode 100644 index 0000000000..4c71ae1182 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedNyeDokumenter.ts" @@ -0,0 +1,23 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import { Dokumenttype } from '../../src/types/Dokument'; +import createMockedDokumentelementLinks from './createMockedDokumentelementLinks'; + +dayjs.extend(utc); + +export default [ + { + id: '1', + type: Dokumenttype.LEGEERKLÆRING, + navn: 'Bar.pdf', + datert: dayjs('2039-01-01').utc(true).toISOString(), + links: createMockedDokumentelementLinks('1'), + }, + { + id: '2', + type: Dokumenttype.LEGEERKLÆRING, + navn: 'Baz.pdf', + datert: dayjs('2039-01-01').utc(true).toISOString(), + links: createMockedDokumentelementLinks('2'), + }, +]; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedTilsynsbehovVurderinger.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedTilsynsbehovVurderinger.ts" new file mode 100644 index 0000000000..b37064f664 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedTilsynsbehovVurderinger.ts" @@ -0,0 +1,60 @@ +import Vurderingsresultat from '../../src/types/Vurderingsresultat'; +import mockedDokumentliste from './mockedDokumentliste'; + +const tilsynsbehovVurderingerMock = [ + { + id: '1', + type: 'KONTINUERLIG_TILSYN_OG_PLEIE', + versjoner: [ + { + perioder: [{ fom: '2022-02-01', tom: '2022-02-15' } as any], + resultat: Vurderingsresultat.OPPFYLT, + dokumenter: mockedDokumentliste, + tekst: 'Fordi her er det behov', + endretAv: 'Z199493', + endretTidspunkt: '2021-10-20T13:23:44.995', + }, + ], + erInnleggelsesperiode: false, + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, + }, + { + id: '2', + type: 'KONTINUERLIG_TILSYN_OG_PLEIE', + versjoner: [ + { + perioder: [{ fom: '2022-01-20', tom: '2022-01-31' } as any], + resultat: Vurderingsresultat.OPPFYLT, + dokumenter: mockedDokumentliste, + tekst: 'Fordi her er det behov', + }, + ], + erInnleggelsesperiode: true, + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, + }, + { + id: '3', + type: 'KONTINUERLIG_TILSYN_OG_PLEIE', + versjoner: [ + { + perioder: [{ fom: '2022-01-15', tom: '2022-01-19' } as any], + resultat: Vurderingsresultat.IKKE_OPPFYLT, + dokumenter: mockedDokumentliste, + tekst: 'Fordi her er det behov', + }, + ], + erInnleggelsesperiode: true, + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, + }, +]; + +export default tilsynsbehovVurderingerMock; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedTilsynsbehovVurderingsoversikt.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedTilsynsbehovVurderingsoversikt.ts" new file mode 100644 index 0000000000..55439de016 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedTilsynsbehovVurderingsoversikt.ts" @@ -0,0 +1,52 @@ +import Vurderingsresultat from '../../src/types/Vurderingsresultat'; +import mockedVurderingsoversiktLinks from './mockedVurderingsoversiktLinks'; +import createMockedVurderingselementLinks from './createMockedVurderingselementLinks'; + +const tilsynsbehovVurderingsoversiktMock = { + vurderingselementer: [ + { + id: '1', + periode: { fom: '2022-02-01', tom: '2022-02-15' } as any, + resultat: Vurderingsresultat.OPPFYLT, + gjelderForSøker: false, + gjelderForAnnenPart: true, + links: createMockedVurderingselementLinks('1'), + endretIDenneBehandlingen: false, + erInnleggelsesperiode: false, + }, + { + id: '2', + periode: { fom: '2022-01-20', tom: '2022-01-31' } as any, + resultat: Vurderingsresultat.OPPFYLT, + gjelderForSøker: false, + gjelderForAnnenPart: true, + links: createMockedVurderingselementLinks('2'), + endretIDenneBehandlingen: false, + erInnleggelsesperiode: true, + }, + { + id: '3', + periode: { fom: '2022-01-15', tom: '2020-01-19' } as any, + resultat: Vurderingsresultat.IKKE_OPPFYLT, + gjelderForSøker: false, + gjelderForAnnenPart: true, + links: createMockedVurderingselementLinks('3'), + endretIDenneBehandlingen: false, + erInnleggelsesperiode: true, + }, + { + id: '4', + periode: { fom: '2022-01-01', tom: '2020-01-14' } as any, + erInnleggelsesperiode: true, + }, + ], + resterendeVurderingsperioder: [{ fom: '2022-02-16', tom: '2022-03-01' } as any], + perioderSomKanVurderes: [{ fom: '2022-01-15', tom: '2022-03-01' } as any], + resterendeValgfrieVurderingsperioder: [], + søknadsperioderTilBehandling: [], + links: mockedVurderingsoversiktLinks, + pleietrengendesFødselsdato: '2004-02-28', + harPerioderDerPleietrengendeErOver18år: true, +}; + +export default tilsynsbehovVurderingsoversiktMock; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedToOmsorgspersonerVurderinger.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedToOmsorgspersonerVurderinger.ts" new file mode 100644 index 0000000000..6e4cd2199f --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedToOmsorgspersonerVurderinger.ts" @@ -0,0 +1,61 @@ +import Vurderingsresultat from '../../src/types/Vurderingsresultat'; +import mockedDokumentliste from './mockedDokumentliste'; +import Vurderingstype from '../../src/types/Vurderingstype'; + +const toOmsorgspersonerVurderingerMock = [ + { + id: '11', + type: 'TO_OMSORGSPERSONER', + versjoner: [ + { + perioder: [{ fom: '2022-02-01', tom: '2020-02-15' } as any], + resultat: Vurderingsresultat.OPPFYLT, + dokumenter: mockedDokumentliste, + tekst: 'Fordi her er det behov', + endretAv: 'Z133337', + endretTidspunkt: '2021-10-20T13:23:44.995', + }, + ], + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, + erInnleggelsesperiode: false, + }, + { + id: '22', + type: 'TO_OMSORGSPERSONER', + versjoner: [ + { + perioder: [{ fom: '2022-01-20', tom: '2022-01-31' } as any], + resultat: Vurderingsresultat.OPPFYLT, + dokumenter: mockedDokumentliste, + tekst: 'Fordi her er det ikke behov', + }, + ], + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, + erInnleggelsesperiode: true, + }, + { + id: '33', + type: 'TO_OMSORGSPERSONER', + versjoner: [ + { + perioder: [{ fom: '2022-01-15', tom: '2022-01-19' } as any], + resultat: Vurderingsresultat.IKKE_OPPFYLT, + dokumenter: mockedDokumentliste, + tekst: 'Fordi her er det ikke behov', + }, + ], + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, + erInnleggelsesperiode: true, + }, +]; + +export default toOmsorgspersonerVurderingerMock; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedToOmsorgspersonerVurderingsoversikt.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedToOmsorgspersonerVurderingsoversikt.ts" new file mode 100644 index 0000000000..e6b9f4baa7 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedToOmsorgspersonerVurderingsoversikt.ts" @@ -0,0 +1,52 @@ +import Vurderingsresultat from '../../src/types/Vurderingsresultat'; +import mockedVurderingsoversiktLinks from './mockedVurderingsoversiktLinks'; +import createMockedVurderingselementLinks from './createMockedVurderingselementLinks'; + +const mockedToOmsorgspersonerVurderingsoversikt = { + vurderingselementer: [ + { + id: '11', + periode: { fom: '2022-02-01', tom: '2022-02-15' } as any, + resultat: Vurderingsresultat.OPPFYLT, + gjelderForSøker: false, + gjelderForAnnenPart: true, + links: createMockedVurderingselementLinks('11'), + endretIDenneBehandlingen: false, + erInnleggelsesperiode: false, + }, + { + id: '22', + periode: { fom: '2022-01-20', tom: '2022-01-31' } as any, + resultat: Vurderingsresultat.OPPFYLT, + gjelderForSøker: false, + gjelderForAnnenPart: true, + links: createMockedVurderingselementLinks('22'), + endretIDenneBehandlingen: false, + erInnleggelsesperiode: true, + }, + { + id: '33', + periode: { fom: '2022-01-15', tom: '2022-01-19' } as any, + resultat: Vurderingsresultat.IKKE_OPPFYLT, + gjelderForSøker: false, + gjelderForAnnenPart: true, + links: createMockedVurderingselementLinks('33'), + endretIDenneBehandlingen: false, + erInnleggelsesperiode: true, + }, + { + id: '55', + periode: { fom: '2022-01-01', tom: '2022-01-14' } as any, + erInnleggelsesperiode: true, + }, + ], + resterendeVurderingsperioder: [{ fom: '2022-02-16', tom: '2022-03-01' } as any], + perioderSomKanVurderes: [{ fom: '2022-01-15', tom: '2022-03-01' } as any], + resterendeValgfrieVurderingsperioder: [{ fom: '2022-01-15', tom: '2022-03-01' } as any], + søknadsperioderTilBehandling: [], + links: mockedVurderingsoversiktLinks, + pleietrengendesFødselsdato: '2021-04-27', + harPerioderDerPleietrengendeErOver18år: true, +}; + +export default mockedToOmsorgspersonerVurderingsoversikt; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedVurderingsoversiktLinks.ts" "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedVurderingsoversiktLinks.ts" new file mode 100644 index 0000000000..5b7deea41c --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/mock/mocked-data/mockedVurderingsoversiktLinks.ts" @@ -0,0 +1,22 @@ +import Link from '../../src/types/Link'; +import LinkRel from '../../src/constants/LinkRel'; + +const links: Link[] = [ + { + rel: LinkRel.OPPRETT_VURDERING, + type: 'POST', + href: 'http://localhost:8082/mock/opprett-vurdering', + versjon: null, + requestPayload: { + behandlingUuid: 'HER_ER_BEHANDLINGSID', + }, + }, + { + rel: LinkRel.DATA_TIL_VURDERING, + type: 'GET', + href: 'http://localhost:8082/mock/data-til-vurdering', + versjon: null, + }, +]; + +export default links; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/package.json" "b/packages/fakta-medisinsk-vilk\303\245r/package.json" new file mode 100644 index 0000000000..3f329fce78 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/package.json" @@ -0,0 +1,37 @@ +{ + "name": "@k9-sak-web/fakta-medisinsk-vilkar", + "version": "1.0.0", + "module": "index.ts", + "keywords": [], + "author": "NAV IT", + "license": "MIT", + "private": true, + "scripts": { + "dev": "node webpack/webpack-config.development.js" + }, + "dependencies": { + "@fpsak-frontend/form": "1.0.0", + "@navikt/diagnosekoder": "^1.2023.0", + "@navikt/ds-css": "5.11.2", + "@navikt/ds-icons": "3.4.3", + "@navikt/ds-react": "5.11.2", + "@navikt/fnrvalidator": "1.x", + "@navikt/ft-plattform-komponenter": "2.3.14", + "@popperjs/core": "2.11.8", + "axios": "1.6.2", + "classnames": "2.3.2", + "dayjs": "1.11.10", + "lodash.throttle": "4.1.1", + "react": "18.2.0", + "react-collapse": "5.1.1", + "react-dom": "18.2.0", + "react-hook-form": "7.48.2", + "react-modal": "3.16.1", + "react-outside-click-handler": "1.3.0", + "react-popper": "2.3.0", + "react-query": "3.39.3" + }, + "msw": { + "workerDirectory": "public" + } +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/public/mockServiceWorker.js" "b/packages/fakta-medisinsk-vilk\303\245r/public/mockServiceWorker.js" new file mode 100644 index 0000000000..95835ef353 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/public/mockServiceWorker.js" @@ -0,0 +1,302 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (1.3.2). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'; +const activeClientIds = new Set(); + +self.addEventListener('install', function () { + self.skipWaiting(); +}); + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener('message', async function (event) { + const clientId = event.source.id; + + if (!clientId || !self.clients) { + return; + } + + const client = await self.clients.get(clientId); + + if (!client) { + return; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }); + break; + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }); + break; + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId); + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }); + break; + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId); + break; + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId); + + const remainingClients = allClients.filter(client => { + return client.id !== clientId; + }); + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister(); + } + + break; + } + } +}); + +self.addEventListener('fetch', function (event) { + const { request } = event; + const accept = request.headers.get('accept') || ''; + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return; + } + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return; + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return; + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return; + } + + // Generate unique request ID. + const requestId = Math.random().toString(16).slice(2); + + event.respondWith( + handleRequest(event, requestId).catch(error => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ); + return; + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ); + }), + ); +}); + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event); + const response = await getResponse(event, client, requestId); + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + (async function () { + const clonedResponse = response.clone(); + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: clonedResponse.body === null ? null : await clonedResponse.text(), + headers: Object.fromEntries(clonedResponse.headers.entries()), + redirected: clonedResponse.redirected, + }, + }); + })(); + } + + return response; +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId); + + if (client?.frameType === 'top-level') { + return client; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + return allClients + .filter(client => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible'; + }) + .find(client => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id); + }); +} + +async function getResponse(event, client, requestId) { + const { request } = event; + const clonedRequest = request.clone(); + + function passthrough() { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const headers = Object.fromEntries(clonedRequest.headers.entries()); + + // Remove MSW-specific request headers so the bypassed requests + // comply with the server's CORS preflight check. + // Operate with the headers as an object because request "Headers" + // are immutable. + delete headers['x-msw-bypass']; + + return fetch(clonedRequest, { headers }); + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough(); + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough(); + } + + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough(); + } + + // Notify the client that a request has been intercepted. + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.text(), + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }); + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data); + } + + case 'MOCK_NOT_FOUND': { + return passthrough(); + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.data; + const networkError = new Error(message); + networkError.name = name; + + // Rejecting a "respondWith" promise emulates a network error. + throw networkError; + } + } + + return passthrough(); +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel(); + + channel.port1.onmessage = event => { + if (event.data && event.data.error) { + return reject(event.data.error); + } + + resolve(event.data); + }; + + client.postMessage(message, [channel.port2]); + }); +} + +function sleep(timeMs) { + return new Promise(resolve => { + setTimeout(resolve, timeMs); + }); +} + +async function respondWithMock(response) { + await sleep(response.delay); + return new Response(response.body, response); +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/__tests__/SykdomVisual.spec.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/__tests__/SykdomVisual.spec.ts" new file mode 100644 index 0000000000..74490b99db --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/__tests__/SykdomVisual.spec.ts" @@ -0,0 +1,78 @@ +// import puppeteer from 'puppeteer'; + +// Trenger følgende devDeps for å kjøre +// "@types/expect-puppeteer": "^5.0.1", +// "@types/jest-environment-puppeteer": "^5.0.2", +// "@types/puppeteer": "^7.0.4", +// "jest-puppeteer": "^6.2.0", +// "expect-puppeteer": "^6.1.1", +// "puppeteer": "^19.5.2" + +// let browser; +// let page; + +describe.skip('Sykdom visual test', () => { + test.skip('skip', () => undefined); + // beforeAll(async () => { + // browser = await puppeteer.launch({ headless: true, args: ['--font-render-hinting=none'] }); + // page = await browser.newPage(); + // const response = await page.goto('http://localhost:8081/'); + // await page.setViewport({ + // width: 1440, + // height: 900, + // }); + // expect(response.status()).toBe(200); + // }); + // it('ingen visuelle regresjoner dokumentasjon', async () => { + // try { + // await page.waitForSelector('#medisinskVilkår', { timeout: 5_000 }); + // await expect(page).toMatch('Dokumenter til behandling', { timeout: 5_000 }); + // const dokumentasjonFørInput = await page.screenshot({ fullPage: true }); + // expect(dokumentasjonFørInput).toMatchImageSnapshot(); + // await expect(page).toClick('label', { text: 'Ja, legeerklæring fra sykehus/spesialisthelsetjenesten' }); + // await expect(page).toFill('input[id="datertField"]', '101021'); + // await expect(page).toClick('button', { text: 'Bekreft' }); + // await page.waitForSelector('div[data-testid="dokumentasjon-ferdig"]', { timeout: 5_000 }); + // const dokumentasjonEtterInput = await page.screenshot({ fullPage: true }); + // expect(dokumentasjonEtterInput).toMatchImageSnapshot(); + // await expect(page).toClick('button', { text: 'Fortsett' }); + // } catch (e) { + // console.log(e); + // await browser.close(); + // } + // }); + // it('ingen visuelle regresjoner tilsyn og pleie', async () => { + // try { + // await expect(page).toMatch('Vurdering av tilsyn og pleie', { timeout: 5_000 }); + // await expect(page).toMatch('Bekreft', { timeout: 5_000 }); + // const tilsynOgPleieFørInput = await page.screenshot({ fullPage: true }); + // expect(tilsynOgPleieFørInput).toMatchImageSnapshot(); + // await expect(page).toClick('input[type="checkbox"]'); + // await expect(page).toFill('textarea[name="vurderingAvKontinuerligTilsynOgPleie"]', 'test'); + // await expect(page).toClick('input[id="harBehovForKontinuerligTilsynOgPleieYES"]'); + // await expect(page).toClick('button', { text: 'Bekreft' }); + // const tilsynOgPleieModal = await page.screenshot({ fullPage: true }); + // expect(tilsynOgPleieModal).toMatchImageSnapshot(); + // await expect(page).toClick('button[data-testid="modal-confirm-button"]'); + // await expect(page).toMatch('Alle perioder', { timeout: 5_000 }); + // const tilsynOgPleieEtterInput = await page.screenshot({ fullPage: true }); + // expect(tilsynOgPleieEtterInput).toMatchImageSnapshot(); + // await expect(page).toClick('button', { text: 'Eventuelle endringer er registrert' }); + // } catch (e) { + // console.log(e); + // await browser.close(); + // } + // }); + // it('ingen visuelle regresjoner i to omsorgspersoner', async () => { + // try { + // await expect(page).toMatch('Vurdering av to omsorgspersoner', { timeout: 5_000 }); + // await expect(page).toMatch('Bekreft', { timeout: 5_000 }); + // const tomOmsorgspersonerFørInput = await page.screenshot({ fullPage: true }); + // expect(tomOmsorgspersonerFørInput).toMatchImageSnapshot(); + // } catch (e) { + // console.log(e); + // } finally { + // await browser.close(); + // } + // }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/api/api.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/api/api.ts" new file mode 100644 index 0000000000..23d6d9eb36 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/api/api.ts" @@ -0,0 +1,122 @@ +import { httpUtils, Period } from '@fpsak-frontend/utils'; +import { Vurderingsversjon } from '../types/Vurdering'; +import Vurderingstype from '../types/Vurderingstype'; +import { PerioderMedEndringResponse } from '../types/PeriodeMedEndring'; +import { RequestPayload } from '../types/RequestPayload'; + +type HttpErrorHandler = (statusCode: number, locationHeader?: string) => void; + +type VurderingsversjonMedType = Partial & { + type: Vurderingstype; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyType = any; + +export async function postNyVurdering( + href: string, + behandlingUuid: string, + vurderingsversjonMedType: VurderingsversjonMedType, + httpErrorHandler: HttpErrorHandler, + signal?: AbortSignal, + dryRun?: boolean, +): Promise { + try { + const { perioder, resultat, tekst, dokumenter, type } = vurderingsversjonMedType; + return httpUtils.post( + href, + { + behandlingUuid, + type, + perioder, + resultat, + tekst, + tilknyttedeDokumenter: dokumenter.map(dokument => dokument.id), + dryRun: dryRun || false, + }, + httpErrorHandler, + { signal }, + ); + } catch (error) { + throw new Error(error); + } +} + +export async function postNyVurderingDryRun( + href: string, + behandlingUuid: string, + vurderingsversjonMedType: VurderingsversjonMedType, + httpErrorHandler: HttpErrorHandler, + signal?: AbortSignal, +): Promise { + return postNyVurdering(href, behandlingUuid, vurderingsversjonMedType, httpErrorHandler, signal, true); +} + +export async function postEndreVurdering( + href: string, + behandlingUuid: string, + vurderingsid: string, + vurderingsversjon: Partial, + httpErrorHandler: HttpErrorHandler, + signal?: AbortSignal, + dryRun?: boolean, +): Promise { + try { + const { perioder, resultat, tekst, dokumenter, versjon } = vurderingsversjon; + return httpUtils.post( + href, + { + behandlingUuid, + id: vurderingsid, + versjon, + tekst, + resultat, + perioder, + tilknyttedeDokumenter: dokumenter.map(dokument => dokument.id), + dryRun: dryRun || false, + }, + httpErrorHandler, + { signal }, + ); + } catch (error) { + throw new Error(error); + } +} + +export async function postEndreVurderingDryRun( + href: string, + behandlingUuid: string, + vurderingsid: string, + vurderingsversjon: Vurderingsversjon, + httpErrorHandler: HttpErrorHandler, + signal?: AbortSignal, +): Promise { + return postEndreVurdering(href, behandlingUuid, vurderingsid, vurderingsversjon, httpErrorHandler, signal, true); +} + +interface InnleggelsesperioderRequestBody extends RequestPayload { + perioder: Period[]; +} + +export interface InnleggelsesperiodeDryRunResponse { + førerTilRevurdering: boolean; +} + +export async function postInnleggelsesperioder( + href: string, + body: InnleggelsesperioderRequestBody, + httpErrorHandler: HttpErrorHandler, + signal?: AbortSignal, + dryRun?: boolean, +): Promise { + return httpUtils.post(href, { ...body, dryRun: dryRun || false }, httpErrorHandler, { signal }); +} + +export async function postInnleggelsesperioderDryRun( + href: string, + body: InnleggelsesperioderRequestBody, + httpErrorHandler: HttpErrorHandler, + signal?: AbortSignal, +): Promise { + return postInnleggelsesperioder(href, body, httpErrorHandler, signal, true); +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/constants/BehandlingType.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/constants/BehandlingType.ts" new file mode 100644 index 0000000000..cc9460b004 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/constants/BehandlingType.ts" @@ -0,0 +1,13 @@ +enum BehandlingType { + FORSTEGANGSSOKNAD = 'BT-002', + KLAGE = 'BT-003', + UNNTAK = 'BT-010', + REVURDERING = 'BT-004', + SOKNAD = 'BT-005', + DOKUMENTINNSYN = 'BT-006', + TILBAKEKREVING = 'BT-007', + ANKE = 'BT-008', + TILBAKEKREVING_REVURDERING = 'BT-009', +} + +export default BehandlingType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/constants/FagsakYtelseType.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/constants/FagsakYtelseType.ts" new file mode 100644 index 0000000000..efc4e231dc --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/constants/FagsakYtelseType.ts" @@ -0,0 +1,15 @@ +enum FagsakYtelseType { + ENGANGSSTONAD = 'ES', + FORELDREPENGER = 'FP', + SVANGERSKAPSPENGER = 'SVP', + PLEIEPENGER = 'PSB', + OMSORGSPENGER = 'OMP', + OMSORGSPENGER_MIDLERTIDIG_ALENE = 'OMP_MA', + OMSORGSPENGER_KRONISK_SYKT_BARN = 'OMP_KS', + OMSORGSPENGER_ALENE_OM_OMSORGEN = 'OMP_AO', + FRISINN = 'FRISINN', + PLEIEPENGER_SLUTTFASE = 'PPN', + OPPLÆRINGSPENGER = 'OLP', +} + +export default FagsakYtelseType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/constants/LinkRel.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/constants/LinkRel.ts" new file mode 100644 index 0000000000..fbe9d63134 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/constants/LinkRel.ts" @@ -0,0 +1,13 @@ +enum LinkRel { + HENT_VURDERING = 'sykdom-vurdering', + OPPRETT_VURDERING = 'sykdom-vurdering-opprettelse', + ENDRE_VURDERING = 'sykdom-vurdering-endring', + DATA_TIL_VURDERING = 'data-til-vurdering', + ENDRE_DOKUMENT = 'sykdom-dokument-endring', + ENDRE_INNLEGGELSESPERIODER = 'sykdom-innleggelse-endring', + ENDRE_DIAGNOSEKODER = 'sykdom-diagnosekoder-endring', + DOKUMENT_INNHOLD = 'sykdom-dokument-innhold', + STATUS = 'sykdom-aksjonspunkt', +} + +export default LinkRel; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/dev/app.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/dev/app.ts" new file mode 100644 index 0000000000..8575b2d091 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/dev/app.ts" @@ -0,0 +1,13 @@ +import renderers from '../util/renderers'; +import ContainerContract from '../types/ContainerContract'; +import '@navikt/ft-plattform-komponenter/dist/style.css'; +import '@navikt/ds-css'; + +interface ExtendedWindow extends Window { + renderMedisinskVilkarApp: (id: string, contract: ContainerContract) => void; +} + +(window as Partial).renderMedisinskVilkarApp = async (appId, data: ContainerContract) => { + const { renderAppInSuccessfulState } = renderers; + renderAppInSuccessfulState(appId, data); +}; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/mocks/browser.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/mocks/browser.ts" new file mode 100644 index 0000000000..1cd1290daf --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/mocks/browser.ts" @@ -0,0 +1,6 @@ +/* eslint-disable import/prefer-default-export */ +/* eslint-disable import/no-extraneous-dependencies */ +import { setupWorker } from 'msw'; +import { handlers } from '../../mock/handlers'; +// This configures a Service Worker with the given request handlers. +export const worker = setupWorker(...handlers); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/ContainerContract.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/ContainerContract.ts" new file mode 100644 index 0000000000..40d7701847 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/ContainerContract.ts" @@ -0,0 +1,28 @@ +import BehandlingType from '../constants/BehandlingType'; +import FagsakYtelseType from '../constants/FagsakYtelseType'; + +interface ContainerContract { + endpoints: { + vurderingsoversiktKontinuerligTilsynOgPleie: string; + vurderingsoversiktBehovForToOmsorgspersoner: string; + vurderingsoversiktLivetsSluttfase?: string; + vurderingsoversiktLangvarigSykdom?: string; + dokumentoversikt: string; + innleggelsesperioder: string; + diagnosekoder: string; + dataTilVurdering: string; + status: string; + nyeDokumenter: string; + }; + behandlingUuid: string; + readOnly: boolean; + onFinished: (...args: unknown[]) => void; + httpErrorHandler: (statusCode: number, locationHeader?: string) => void; + visFortsettknapp: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + saksbehandlere: any; + fagsakYtelseType?: FagsakYtelseType; + behandlingType?: BehandlingType; +} + +export default ContainerContract; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Diagnosekode.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Diagnosekode.ts" new file mode 100644 index 0000000000..692f91f794 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Diagnosekode.ts" @@ -0,0 +1,8 @@ +interface Diagnosekode { + kode: string; + beskrivelse: string; +} + +export type DiagnosekodeWrapper = { koder: Array; hasLoaded: boolean }; + +export default Diagnosekode; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/DiagnosekodeResponse.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/DiagnosekodeResponse.ts" new file mode 100644 index 0000000000..88b50794a3 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/DiagnosekodeResponse.ts" @@ -0,0 +1,8 @@ +import Link from './Link'; + +export interface DiagnosekodeResponse { + diagnosekoder: string[]; + links?: Link[]; + behandlingUuid: string; + versjon: string; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Dokument.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Dokument.ts" new file mode 100644 index 0000000000..76ee9852df --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Dokument.ts" @@ -0,0 +1,42 @@ +import Link from './Link'; + +export enum Dokumenttype { + LEGEERKLÆRING = 'LEGEERKLÆRING_SYKEHUS', + EPIKRISE = 'EPIKRISE', + ANDRE_MEDISINSKE_OPPLYSNINGER = 'MEDISINSKE_OPPLYSNINGER', + MANGLER_MEDISINSKE_OPPLYSNINGER = 'ANNET', + UKLASSIFISERT = 'UKLASSIFISERT', + LEGEERKLÆRING_ANNEN = 'LEGEERKLÆRING_ANNEN', + LEGEERKLÆRING_MED_DOKUMENTASJON_AV_OPPLÆRING = 'LEGEERKLÆRING_MED_DOKUMENTASJON_AV_OPPLÆRING', + DOKUMENTASJON_AV_OPPLÆRING = 'DOKUMENTASJON_AV_OPPLÆRING', +} + +export const dokumentLabel = { + LEGEERKLÆRING_SYKEHUS: 'Sykehus/spesialist.', + MEDISINSKE_OPPLYSNINGER: 'Andre med. oppl.', + ANNET: 'Ikke med. oppl.', + UKLASSIFISERT: 'Ikke klassifisert', + LEGEERKLÆRING_ANNEN: 'Legeerklæring', + LEGEERKLÆRING_MED_DOKUMENTASJON_AV_OPPLÆRING: 'Legeerklæring/kursdok.', + DOKUMENTASJON_AV_OPPLÆRING: 'Kursdok.', + EPIKRISE: 'Epikrise', +}; + +export interface Dokument { + annenPartErKilde: boolean; + behandlet: boolean; + benyttet: boolean; + bruktTilMinstEnVurdering: boolean; + datert: string; + duplikatAvId: string; + duplikater: string[]; + fremhevet: boolean; + id: string; + links: Link[]; + mottattDato: string; + mottattTidspunkt: string; + navn: string; + type: Dokumenttype; +} + +export default Dokument; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Dokumentoversikt.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Dokumentoversikt.ts" new file mode 100644 index 0000000000..5b94fd282b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Dokumentoversikt.ts" @@ -0,0 +1,40 @@ +import { dokumentSorter } from '../util/dokumentUtils'; +import Dokument, { Dokumenttype } from './Dokument'; + +class Dokumentoversikt { + alleDokumenter: Dokument[]; + + strukturerteDokumenter: Dokument[]; + + ustrukturerteDokumenter: Dokument[]; + + constructor(dokumenter: Dokument[]) { + this.alleDokumenter = dokumenter; + this.strukturerteDokumenter = dokumenter + .filter(({ type }) => type !== Dokumenttype.UKLASSIFISERT) + .sort(dokumentSorter); + this.ustrukturerteDokumenter = dokumenter + .filter(({ type }) => type === Dokumenttype.UKLASSIFISERT) + .sort(dokumentSorter); + } + + harGyldigSignatur(): boolean { + return this.strukturerteDokumenter.some(({ type }) => + [ + Dokumenttype.LEGEERKLÆRING, + Dokumenttype.LEGEERKLÆRING_ANNEN, + Dokumenttype.LEGEERKLÆRING_MED_DOKUMENTASJON_AV_OPPLÆRING, + ].includes(type), + ); + } + + harDokumenter(): boolean { + return this.strukturerteDokumenter.length > 0 || this.ustrukturerteDokumenter.length > 0; + } + + harUstrukturerteDokumenter(): boolean { + return this.ustrukturerteDokumenter.length > 0; + } +} + +export default Dokumentoversikt; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/DokumentoversiktResponse.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/DokumentoversiktResponse.ts" new file mode 100644 index 0000000000..200250f355 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/DokumentoversiktResponse.ts" @@ -0,0 +1,5 @@ +import Dokument from './Dokument'; + +export interface DokumentoversiktResponse { + dokumenter: Dokument[]; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/GradAvTilsynsbehov.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/GradAvTilsynsbehov.ts" new file mode 100644 index 0000000000..2d8d00a76e --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/GradAvTilsynsbehov.ts" @@ -0,0 +1,8 @@ +enum GradAvTilsynsbehov { + BEHOV_FOR_EN = 'behovForEn', + BEHOV_FOR_TO = 'behovForTo', + IKKE_BEHOV = 'ikkeBehov', + INNLAGT = 'innlagt', +} + +export default GradAvTilsynsbehov; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/InnleggelsesperiodeResponse.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/InnleggelsesperiodeResponse.ts" new file mode 100644 index 0000000000..f9f8af2b55 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/InnleggelsesperiodeResponse.ts" @@ -0,0 +1,9 @@ +import { Period } from '@fpsak-frontend/utils'; +import Link from './Link'; + +export interface InnleggelsesperiodeResponse { + behandlingUuid: string; + versjon: string; + perioder: Period[]; + links: Link[]; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/InnleggelsesperiodeVurdering.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/InnleggelsesperiodeVurdering.ts" new file mode 100644 index 0000000000..57ebf7ba50 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/InnleggelsesperiodeVurdering.ts" @@ -0,0 +1,9 @@ +import { Period } from '@fpsak-frontend/utils'; + +interface InnleggelsesperiodeVurdering { + id: string; + periode: Period; + erInnleggelsesperiode: true; +} + +export default InnleggelsesperiodeVurdering; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Link.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Link.ts" new file mode 100644 index 0000000000..8666d7e1f8 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Link.ts" @@ -0,0 +1,13 @@ +import LinkRel from '../constants/LinkRel'; + +interface Link { + versjon: string; + rel: LinkRel; + type: 'GET' | 'POST'; + href: string; + requestPayload?: { + behandlingUuid: string; + }; +} + +export default Link; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/ManuellVurdering.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/ManuellVurdering.ts" new file mode 100644 index 0000000000..33f46965ac --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/ManuellVurdering.ts" @@ -0,0 +1,16 @@ +import { Period } from '@fpsak-frontend/utils'; +import Vurderingsresultat from './Vurderingsresultat'; +import Link from './Link'; + +interface ManuellVurdering { + id: string; + resultat: Vurderingsresultat; + periode: Period; + gjelderForSøker: boolean; + gjelderForAnnenPart: boolean; + links: Link[]; + endretIDenneBehandlingen: boolean; + erInnleggelsesperiode: boolean; +} + +export default ManuellVurdering; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/NyVurderingsversjon.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/NyVurderingsversjon.ts" new file mode 100644 index 0000000000..0ff85b35b0 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/NyVurderingsversjon.ts" @@ -0,0 +1,15 @@ +import { Period } from '@fpsak-frontend/utils'; +import Dokument from './Dokument'; +import Vurderingsresultat from './Vurderingsresultat'; + +export default interface NyVurderingsversjon { + behandlingUuid: string; + perioder: Period[]; + resultat: Vurderingsresultat; + tekst: string; + tilknyttedeDokumenter: Dokument[]; + type: string; + id?: string; + versjon?: string; + dryRun?: boolean; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/NyeDokumenterResponse.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/NyeDokumenterResponse.ts" new file mode 100644 index 0000000000..90164ebd16 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/NyeDokumenterResponse.ts" @@ -0,0 +1,3 @@ +import Dokument from './Dokument'; + +export type NyeDokumenterResponse = Dokument[]; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/PeriodeMedEndring.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/PeriodeMedEndring.ts" new file mode 100644 index 0000000000..e9ca2f8514 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/PeriodeMedEndring.ts" @@ -0,0 +1,11 @@ +import { Period } from '@fpsak-frontend/utils'; + +export interface PerioderMedEndringResponse { + perioderMedEndringer: PeriodeMedEndring[]; +} + +export interface PeriodeMedEndring { + periode: Period; + endrerVurderingSammeBehandling: boolean; + endrerAnnenVurdering: boolean; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/PeriodeMedGradAvTilsynsbehov.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/PeriodeMedGradAvTilsynsbehov.ts" new file mode 100644 index 0000000000..b552717a1d --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/PeriodeMedGradAvTilsynsbehov.ts" @@ -0,0 +1,9 @@ +import { Period } from '@fpsak-frontend/utils'; +import GradAvTilsynsbehov from './GradAvTilsynsbehov'; + +interface PeriodeMedGradAvTilsynsbehov { + periode: Period; + grad: GradAvTilsynsbehov; +} + +export default PeriodeMedGradAvTilsynsbehov; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/RequestPayload.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/RequestPayload.ts" new file mode 100644 index 0000000000..d4f5bdff11 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/RequestPayload.ts" @@ -0,0 +1,4 @@ +export interface RequestPayload { + behandlingUuid: string; + versjon?: string; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Step.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Step.ts" new file mode 100644 index 0000000000..e7d109b4c0 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Step.ts" @@ -0,0 +1,48 @@ +export enum StepId { + Dokument = 'dokument', + TilsynOgPleie = 'tilsynOgPleie', + ToOmsorgspersoner = 'toOmsorgspersoner', + LivetsSluttfase = 'livetsSluttfase', + LangvarigSykdom = 'langvarigSykdom', +} + +interface Step { + id: StepId; + title: string; +} + +export const dokumentSteg: Step = { + id: StepId.Dokument, + title: 'Dokumentasjon av sykdom', +}; + +export const sluttfaseDokumentSteg: Step = { + id: StepId.Dokument, + title: 'Dokumentasjon av livets sluttfase', +}; + +export const opplæringspengerDokumentSteg: Step = { + id: StepId.Dokument, + title: 'Dokumentasjon av opplæringspenger', +}; + +export const tilsynOgPleieSteg: Step = { + id: StepId.TilsynOgPleie, + title: 'Tilsyn og pleie', +}; + +export const livetsSluttfaseSteg: Step = { + id: StepId.LivetsSluttfase, + title: 'Livets sluttfase', +}; +export const langvarigSykdomSteg: Step = { + id: StepId.LangvarigSykdom, + title: 'Langvarig sykdom', +}; + +export const toOmsorgspersonerSteg: Step = { + id: StepId.ToOmsorgspersoner, + title: 'To omsorgspersoner', +}; + +export default Step; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/StrukturerDokumentFormProps.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/StrukturerDokumentFormProps.ts" new file mode 100644 index 0000000000..0f32a3e845 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/StrukturerDokumentFormProps.ts" @@ -0,0 +1,11 @@ +import { Dokument } from './Dokument'; + +interface StrukturerDokumentFormProps { + dokument: Dokument; + onSubmit: (nyttDokument: Dokument) => void; + editMode?: boolean; + isSubmitting: boolean; + strukturerteDokumenter: Dokument[]; +} + +export default StrukturerDokumentFormProps; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/StrukturerDokumentFormState.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/StrukturerDokumentFormState.ts" new file mode 100644 index 0000000000..44dc6940af --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/StrukturerDokumentFormState.ts" @@ -0,0 +1,13 @@ +import { Dokumenttype } from './Dokument'; + +export enum StrukturerDokumentFormFieldName { + INNEHOLDER_MEDISINSKE_OPPLYSNINGER = 'inneholderMedisinskeOpplysninger', + DATERT = 'datert', + DUPLIKAT_AV_ID = 'duplikatAvId', +} + +export interface StrukturerDokumentFormState { + [StrukturerDokumentFormFieldName.INNEHOLDER_MEDISINSKE_OPPLYSNINGER]?: Dokumenttype; + [StrukturerDokumentFormFieldName.DATERT]: string; + [StrukturerDokumentFormFieldName.DUPLIKAT_AV_ID]: string; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/SykdomsstegStatusResponse.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/SykdomsstegStatusResponse.ts" new file mode 100644 index 0000000000..bde42ed895 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/SykdomsstegStatusResponse.ts" @@ -0,0 +1,14 @@ +interface SykdomsstegStatusResponse { + kanLøseAksjonspunkt: boolean; + harDataSomIkkeHarBlittTattMedIBehandling: boolean; + harUklassifiserteDokumenter: boolean; + manglerDiagnosekode?: boolean; + manglerGodkjentLegeerklæring: boolean; + manglerVurderingAvKontinuerligTilsynOgPleie?: boolean; + manglerVurderingAvToOmsorgspersoner?: boolean; + nyttDokumentHarIkkekontrollertEksisterendeVurderinger: boolean; + manglerVurderingAvILivetsSluttfase?: true; + manglerVurderingAvLangvarigSykdom?: boolean; +} + +export default SykdomsstegStatusResponse; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Tilsynsbehov.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Tilsynsbehov.ts" new file mode 100644 index 0000000000..37c29dbaed --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Tilsynsbehov.ts" @@ -0,0 +1,7 @@ +enum Tilsynsbehov { + HELE = 'hele', + DELER = 'deler', + INGEN = 'ingen', +} + +export default Tilsynsbehov; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurdering.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurdering.ts" new file mode 100644 index 0000000000..073f07cc58 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurdering.ts" @@ -0,0 +1,54 @@ +import { Period } from '@fpsak-frontend/utils'; +import Dokument from './Dokument'; +import Vurderingsresultat from './Vurderingsresultat'; + +export interface AnnenInformasjon { + resterendeVurderingsperioder: Period[]; + perioderSomKanVurderes: Period[]; +} + +export interface Vurderingsversjon { + endretAv: string; + endretTidspunkt: string; + versjon?: string; + tekst: string; + resultat: Vurderingsresultat; + perioder: Period[]; + dokumenter: Dokument[]; +} + +class Vurdering { + id: string; + + type: string; + + versjoner: Vurderingsversjon[]; + + annenInformasjon: AnnenInformasjon; + + erInnleggelsesperiode: boolean; + + constructor({ + id, + type, + versjoner, + annenInformasjon: { resterendeVurderingsperioder, perioderSomKanVurderes }, + erInnleggelsesperiode, + }: Vurdering) { + this.id = id; + this.type = type; + this.erInnleggelsesperiode = erInnleggelsesperiode; + + this.versjoner = versjoner.map(vurderingsversjon => ({ + ...vurderingsversjon, + perioder: vurderingsversjon.perioder.map(({ fom, tom }) => new Period(fom, tom)), + })); + + this.annenInformasjon = { + resterendeVurderingsperioder: resterendeVurderingsperioder.map(({ fom, tom }) => new Period(fom, tom)), + perioderSomKanVurderes: perioderSomKanVurderes.map(({ fom, tom }) => new Period(fom, tom)), + }; + } +} + +export default Vurdering; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/VurderingContext.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/VurderingContext.ts" new file mode 100644 index 0000000000..2148ec3971 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/VurderingContext.ts" @@ -0,0 +1,7 @@ +import Vurderingstype from './Vurderingstype'; + +interface VurderingContextType { + vurderingstype?: Vurderingstype; +} + +export default VurderingContextType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingselement.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingselement.ts" new file mode 100644 index 0000000000..0c087a8ad5 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingselement.ts" @@ -0,0 +1,5 @@ +import ManuellVurdering from './ManuellVurdering'; +import InnleggelsesperiodeVurdering from './InnleggelsesperiodeVurdering'; + +type Vurderingselement = ManuellVurdering | InnleggelsesperiodeVurdering; +export default Vurderingselement; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingsoversikt.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingsoversikt.ts" new file mode 100644 index 0000000000..ca7df64726 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingsoversikt.ts" @@ -0,0 +1,85 @@ +import { Period } from '@fpsak-frontend/utils'; +import Link from './Link'; +import Vurderingselement from './Vurderingselement'; + +export class Vurderingsoversikt { + vurderingselementer: Vurderingselement[]; + + resterendeVurderingsperioder: Period[]; + + resterendeValgfrieVurderingsperioder: Period[]; + + søknadsperioderTilBehandling: Period[]; + + perioderSomKanVurderes: Period[]; + + links: Link[]; + + pleietrengendesFødselsdato: string; + + harPerioderDerPleietrengendeErOver18år: boolean; + + constructor(data: Partial) { + try { + this.perioderSomKanVurderes = data.perioderSomKanVurderes.map(({ fom, tom }) => new Period(fom, tom)); + this.resterendeVurderingsperioder = data.resterendeVurderingsperioder.map(({ fom, tom }) => new Period(fom, tom)); + this.resterendeValgfrieVurderingsperioder = data.resterendeValgfrieVurderingsperioder.map( + ({ fom, tom }) => new Period(fom, tom), + ); + this.søknadsperioderTilBehandling = data.søknadsperioderTilBehandling.map(({ fom, tom }) => new Period(fom, tom)); + this.vurderingselementer = data.vurderingselementer.map(vurderingselement => ({ + ...vurderingselement, + periode: new Period(vurderingselement.periode.fom, vurderingselement.periode.tom), + })); + this.links = data.links; + this.pleietrengendesFødselsdato = data.pleietrengendesFødselsdato; + this.harPerioderDerPleietrengendeErOver18år = data.harPerioderDerPleietrengendeErOver18år; + } catch (error) { + throw new Error(`Processing Vurderingsoversikt\n${error}`); + } + } + + harPerioderÅVise(): boolean { + return ( + this.harPerioderSomSkalVurderes() === true || + this.harVurdertePerioder() === true || + this.harValgfriePerioderSomKanVurderes() === true + ); + } + + harIngenPerioderÅVise(): boolean { + return ( + this.harPerioderSomSkalVurderes() === false && + this.harVurdertePerioder() === false && + this.harValgfriePerioderSomKanVurderes() === false + ); + } + + harPerioderSomSkalVurderes(): boolean { + return this.resterendeVurderingsperioder && this.resterendeVurderingsperioder.length > 0; + } + + harValgfriePerioderSomKanVurderes(): boolean { + return this.resterendeValgfrieVurderingsperioder && this.resterendeValgfrieVurderingsperioder.length > 0; + } + + harVurdertePerioder(): boolean { + return this.vurderingselementer && this.vurderingselementer.length > 0; + } + + finnVurderingsperioderSomOverlapperMedNyeSøknadsperioder(): Period[] { + return ( + this.vurderingselementer + .filter(({ periode }) => { + const vurdertPeriode = new Period(periode.fom, periode.tom); + const overlapperMedEnSøknadsperiode = this.resterendeVurderingsperioder.some(({ fom, tom }) => + vurdertPeriode.overlapsWith(new Period(fom, tom)), + ); + return overlapperMedEnSøknadsperiode; + }) + .map(({ periode }) => periode) || [] + ); + } +} + +export default Vurderingsoversikt; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingsresultat.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingsresultat.ts" new file mode 100644 index 0000000000..0169c6cfdd --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingsresultat.ts" @@ -0,0 +1,7 @@ +enum Vurderingsresultat { + OPPFYLT = 'OPPFYLT', + IKKE_OPPFYLT = 'IKKE_OPPFYLT', + DELVIS_OPPFYLT = 'DELVIS_OPPFYLT', +} + +export default Vurderingsresultat; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingstype.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingstype.ts" new file mode 100644 index 0000000000..635073bc77 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/types/Vurderingstype.ts" @@ -0,0 +1,8 @@ +enum Vurderingstype { + KONTINUERLIG_TILSYN_OG_PLEIE = 'KONTINUERLIG_TILSYN_OG_PLEIE', + TO_OMSORGSPERSONER = 'TO_OMSORGSPERSONER', + LIVETS_SLUTTFASE = 'LIVETS_SLUTTFASE', + LANGVARIG_SYKDOM = 'LANGVARIG_SYKDOM', +} + +export default Vurderingstype; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/MainComponent.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/MainComponent.tsx" new file mode 100644 index 0000000000..c1b1242dd1 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/MainComponent.tsx" @@ -0,0 +1,22 @@ +import React from 'react'; +import { QueryClientProvider } from 'react-query'; +import ContainerContext from './context/ContainerContext'; +import queryClient from './context/queryClient'; +import ContainerContract from '../types/ContainerContract'; +import MedisinskVilkår from './components/medisinsk-vilkår/MedisinskVilkår'; + +interface MainComponentProps { + data: ContainerContract; +} + +const MainComponent = ({ data }: MainComponentProps): JSX.Element => ( +
    + + + + + +
    +); + +export default MainComponent; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/add-button/AddButton.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/add-button/AddButton.tsx" new file mode 100644 index 0000000000..19f46146bc --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/add-button/AddButton.tsx" @@ -0,0 +1,30 @@ +import React, { forwardRef, Ref } from 'react'; +import { PlusIcon } from '@navikt/ft-plattform-komponenter'; +import styles from './addButton.css'; + +interface AddButtonProps { + onClick: () => void; + label: string; + id?: string; + className?: string; + noIcon?: boolean; + ariaLabel?: string; +} + +const AddButton = forwardRef( + ({ className, label, onClick, id, noIcon, ariaLabel }: AddButtonProps, ref?: Ref): JSX.Element => ( + + ), +); + +export default AddButton; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/add-button/addButton.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/add-button/addButton.css" new file mode 100644 index 0000000000..19a76cce21 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/add-button/addButton.css" @@ -0,0 +1,16 @@ +.addButton { + display: flex; + padding: 0; + color: #0067c5; + border: none; + background: none; + height: 23px; + position: relative; + cursor: pointer; + align-items: center; + font-size: 1rem; +} + +.addButton__text { + margin-left: 0.5rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/aksjonspunkt-ferdig-stripe/AksjonspunktFerdigStripe.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/aksjonspunkt-ferdig-stripe/AksjonspunktFerdigStripe.tsx" new file mode 100644 index 0000000000..4f92b19c4d --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/aksjonspunkt-ferdig-stripe/AksjonspunktFerdigStripe.tsx" @@ -0,0 +1,36 @@ +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import { Alert, Button } from '@navikt/ds-react'; +import * as React from 'react'; +import FagsakYtelseType from '../../../constants/FagsakYtelseType'; +import ContainerContext from '../../context/ContainerContext'; + +const AksjonspunktFerdigStripe = (): JSX.Element => { + const { onFinished } = React.useContext(ContainerContext); + const [isSubmitting, setIsSubmitting] = React.useState(false); + const { fagsakYtelseType } = React.useContext(ContainerContext); + const erPleiepengerSluttfaseFagsak = fagsakYtelseType === FagsakYtelseType.PLEIEPENGER_SLUTTFASE; + + return ( + + + {erPleiepengerSluttfaseFagsak && <>Vilkåret er ferdig vurdert og du kan gå videre i behandlingen.} + {!erPleiepengerSluttfaseFagsak && <>Sykdom er ferdig vurdert og du kan gå videre i behandlingen.} + + + + ); +}; + +export default AksjonspunktFerdigStripe; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/confirmation-modal/ConfirmationModal.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/confirmation-modal/ConfirmationModal.tsx" new file mode 100644 index 0000000000..a86ffaa5b3 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/confirmation-modal/ConfirmationModal.tsx" @@ -0,0 +1,48 @@ +import React from 'react'; +import { Button, Modal } from '@navikt/ds-react'; +import styles from './confirmationModal.css'; + +interface ConfirmationModalProps { + children: React.ReactNode; + onConfirm: () => void; + onCancel: () => void; + isOpen: boolean; + isSubmitting: boolean; +} + +const ConfirmationModal = ({ + children, + onConfirm, + onCancel, + isOpen, + isSubmitting, +}: ConfirmationModalProps): JSX.Element => + isOpen ? ( + + + {children} +
    + + +
    +
    +
    + ) : null; + +export default ConfirmationModal; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/confirmation-modal/confirmationModal.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/confirmation-modal/confirmationModal.css" new file mode 100644 index 0000000000..84a5c68af2 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/confirmation-modal/confirmationModal.css" @@ -0,0 +1,7 @@ +.confirmationModal { + color: #262626; +} + +.confirmationModal__buttonSection { + margin-top: 1rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dekket-av-innleggelsesperiode-melding/DekketAvInnleggelsesperiodeMelding.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dekket-av-innleggelsesperiode-melding/DekketAvInnleggelsesperiodeMelding.tsx" new file mode 100644 index 0000000000..78c64759d5 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dekket-av-innleggelsesperiode-melding/DekketAvInnleggelsesperiodeMelding.tsx" @@ -0,0 +1,11 @@ +import React from 'react'; +import { Alert } from '@navikt/ds-react'; + +const DekketAvInnleggelsesperiodeMelding = (): JSX.Element => ( + + Hele eller deler av perioden er oppfylt som følge av innleggelse. Vurderingen som ligger til grunn blir dermed ikke + brukt for disse dagene. + +); + +export default DekketAvInnleggelsesperiodeMelding; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/delete-button/DeleteButton.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/delete-button/DeleteButton.tsx" new file mode 100644 index 0000000000..b8e9b3a51d --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/delete-button/DeleteButton.tsx" @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { BucketIcon } from '@navikt/ft-plattform-komponenter'; +import styles from './deleteButton.css'; + +interface DeleteButtonProps { + onClick: () => void; +} + +const DeleteButton = ({ onClick }: DeleteButtonProps): JSX.Element => ( +
    + +
    +); + +export default DeleteButton; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/delete-button/deleteButton.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/delete-button/deleteButton.css" new file mode 100644 index 0000000000..201484ca4e --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/delete-button/deleteButton.css" @@ -0,0 +1,13 @@ +.deleteButton__container { + margin-left: 1rem; + margin-bottom: 0.5rem; + align-self: flex-end; +} + +.deleteButton__button { + padding: 0; + border: none; + background: none; + height: 23px; + cursor: pointer; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/detail-view-vurdering/DetailViewVurdering.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/detail-view-vurdering/DetailViewVurdering.tsx" new file mode 100644 index 0000000000..80017cfe75 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/detail-view-vurdering/DetailViewVurdering.tsx" @@ -0,0 +1,49 @@ +import { DetailView, DetailViewProps, LinkButton } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import { Period } from '@fpsak-frontend/utils'; +import PeriodList from '../period-list/PeriodList'; +import styles from './detailViewVurdering.css'; +import WriteAccessBoundContent from '../write-access-bound-content/WriteAccessBoundContent'; +import ContainerContext from '../../context/ContainerContext'; +import BehandlingType from '../../../constants/BehandlingType'; +import FagsakYtelseType from '../../../constants/FagsakYtelseType'; + +interface DetailViewVurderingProps extends DetailViewProps { + perioder: Period[]; + redigerVurdering?: () => void; +} + +const DetailViewVurdering = (props: DetailViewVurderingProps): JSX.Element => { + const { children, perioder, redigerVurdering, title } = props; + const { fagsakYtelseType, behandlingType } = React.useContext(ContainerContext); + const harPerioder = perioder.length > 0 && perioder[0].isValid(); + + const skalViseRedigerVurderingKnapp = + fagsakYtelseType === FagsakYtelseType.PLEIEPENGER_SLUTTFASE ? behandlingType !== BehandlingType.REVURDERING : true; + + return ( + + redigerVurdering && ( + + skalViseRedigerVurderingKnapp && ( + + Rediger vurdering + + ) + } + /> + ) + } + > + {harPerioder && } +
    + {children} +
    + ); +}; + +export default DetailViewVurdering; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/detail-view-vurdering/detailViewVurdering.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/detail-view-vurdering/detailViewVurdering.css" new file mode 100644 index 0000000000..453c259dfc --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/detail-view-vurdering/detailViewVurdering.css" @@ -0,0 +1,12 @@ +.detailViewVurdering__periodList { + margin-top: 1.25rem; +} + +.detailViewVurdering__hr { + margin-top: 1rem; + color: #b7b1a9; +} + +.detailViewVurdering__endreLink { + margin-left: 1rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekode-modal/DiagnosekodeModal.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekode-modal/DiagnosekodeModal.tsx" new file mode 100644 index 0000000000..ce9f5b5612 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekode-modal/DiagnosekodeModal.tsx" @@ -0,0 +1,84 @@ +import { Button, Modal } from '@navikt/ds-react'; +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import DiagnosekodeSelector from '../../form/pure/PureDiagnosekodeSelector'; +import styles from '../diagnosekodeoversikt/diagnosekodeoversikt.css'; +import type { DiagnosekodeSearcherPromise } from '../../../util/diagnosekodeSearcher'; + +interface DiagnosekodeModalProps { + isOpen: boolean; + onRequestClose: () => void; + onSaveClick: (diagnosekoder: string[]) => Promise; + searcherPromise: DiagnosekodeSearcherPromise; +} + +const DiagnosekodeModal = ({ + isOpen, + onRequestClose, + onSaveClick, + searcherPromise, +}: DiagnosekodeModalProps): JSX.Element => { + const [selectedDiagnosekoder, setSelectedDiagnosekoder] = React.useState([]); + const [isSubmitting, setIsSubmitting] = React.useState(false); + + const handleClose = () => { + setSelectedDiagnosekoder([]); + onRequestClose(); + }; + + return ( + + +
    { + e.preventDefault(); + e.stopPropagation(); + setIsSubmitting(true); + onSaveClick(selectedDiagnosekoder).then( + () => setTimeout(() => setIsSubmitting(false), 2500), + () => setTimeout(() => setIsSubmitting(false), 2500), + ); + setSelectedDiagnosekoder([]); + }} + > + + { + setSelectedDiagnosekoder(diagnosekoder); + }} + label="Diagnosekode" + selectedDiagnosekoder={selectedDiagnosekoder} + hideLabel + searcherPromise={searcherPromise} + /> + + +
    + + +
    +
    +
    +
    +
    + ); +}; + +export default DiagnosekodeModal; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeliste/Diagnosekodeliste.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeliste/Diagnosekodeliste.tsx" new file mode 100644 index 0000000000..f9b15253fd --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeliste/Diagnosekodeliste.tsx" @@ -0,0 +1,44 @@ +import React from 'react'; +import Diagnosekode from '../../../types/Diagnosekode'; +import WriteAccessBoundContent from '../write-access-bound-content/WriteAccessBoundContent'; +import styles from './diagnosekodeliste.css'; + +interface DiagnosekodelisteProps { + diagnosekoder: Diagnosekode[]; + onDeleteClick: (diagnosekode: string) => void; +} + +const Diagnosekodeliste = ({ diagnosekoder, onDeleteClick }: DiagnosekodelisteProps): JSX.Element => ( +
      + {diagnosekoder.map(diagnosekode => { + const diagnosekodeBeskrivelse = diagnosekode?.beskrivelse + ? `${diagnosekode.kode} - ${diagnosekode.beskrivelse}` + : diagnosekode?.kode; + + return ( +
    • +

      {diagnosekodeBeskrivelse}

      + ( +
      + +
      + )} + /> +
    • + ); + })} +
    +); + +export default Diagnosekodeliste; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeliste/diagnosekodeliste.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeliste/diagnosekodeliste.css" new file mode 100644 index 0000000000..dfea85eeee --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeliste/diagnosekodeliste.css" @@ -0,0 +1,36 @@ +.diagnosekodeliste { + list-style-type: none; + margin: 0; + padding: 0; +} + +.diagnosekodeliste__element { + display: flex; + position: relative; + border-bottom: 1px solid #c8c8c8; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.diagnosekodeliste__element:not(:first-child) { + margin-top: 0.5rem; +} + +.lenkeContainer { + position: absolute; + right: 0; +} + +.lenkeContainer__slettLenke { + border: none; + background: transparent; + cursor: pointer; +} + +.beskrivelse { + margin: 0; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: 80%; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeoversikt/Diagnosekodeoversikt.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeoversikt/Diagnosekodeoversikt.tsx" new file mode 100644 index 0000000000..41ec463a96 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeoversikt/Diagnosekodeoversikt.tsx" @@ -0,0 +1,152 @@ +import { Box, Margin, TitleWithUnderline, WarningIcon } from '@navikt/ft-plattform-komponenter'; +import { httpUtils } from '@fpsak-frontend/utils'; + +import { Loader } from '@navikt/ds-react'; +import React, { useMemo } from 'react'; +import { useMutation, useQueries, useQuery } from 'react-query'; +import LinkRel from '../../../constants/LinkRel'; +import Diagnosekode from '../../../types/Diagnosekode'; +import { DiagnosekodeResponse } from '../../../types/DiagnosekodeResponse'; +import { findLinkByRel } from '../../../util/linkUtils'; +import ContainerContext from '../../context/ContainerContext'; +import AddButton from '../add-button/AddButton'; +import DiagnosekodeModal from '../diagnosekode-modal/DiagnosekodeModal'; +import Diagnosekodeliste from '../diagnosekodeliste/Diagnosekodeliste'; +import IconWithText from '../icon-with-text/IconWithText'; +import WriteAccessBoundContent from '../write-access-bound-content/WriteAccessBoundContent'; +import initDiagnosekodeSearcher, { toLegacyDiagnosekode } from '../../../util/diagnosekodeSearcher'; + +// Start initializing diagnosekode searcher instance, with pagesize 8, so that it can be used both here and in the DiagnosekodeModal. +// This reuse is possible since we don't use the paging functionality in the instance anyways. +const diagnosekodeSearcherPromise = initDiagnosekodeSearcher(8); + +const fetchDiagnosekoderByQuery = async (queryString: string): Promise => { + const searcher = await diagnosekodeSearcherPromise; + const searchResult = searcher.search(queryString, 1); + // This function only returns the found diagnosecode if there is exactly one diagnosecode found. + if (searchResult.diagnosekoder.length === 1 && !searchResult.hasMore) { + return toLegacyDiagnosekode(searchResult.diagnosekoder[0]); + } + return { kode: queryString, beskrivelse: '' }; +}; + +interface DiagnosekodeoversiktProps { + onDiagnosekoderUpdated: () => void; +} + +const Diagnosekodeoversikt = ({ onDiagnosekoderUpdated }: DiagnosekodeoversiktProps): JSX.Element => { + const { endpoints, httpErrorHandler } = React.useContext(ContainerContext); + const [modalIsOpen, setModalIsOpen] = React.useState(false); + const addButtonRef = React.useRef(); + + const hentDiagnosekoder = () => + httpUtils + .get(endpoints.diagnosekoder, httpErrorHandler) + .then((response: DiagnosekodeResponse) => response); + + const { isLoading, data, refetch } = useQuery('diagnosekodeResponse', hentDiagnosekoder); + + const { diagnosekoder, links, behandlingUuid, versjon } = data; + const endreDiagnosekoderLink = findLinkByRel(LinkRel.ENDRE_DIAGNOSEKODER, links); + + const diagnosekoderMedNavnResponses = useQueries( + diagnosekoder.map(diagnosekode => ({ + queryKey: ['diagnosekode', diagnosekode], + queryFn: () => fetchDiagnosekoderByQuery(diagnosekode), + refetchOnWindowFocus: false, + })), + ); + + const diagnosekoderMedNavn = useMemo( + () => diagnosekoderMedNavnResponses.filter(response => !!response.data).map(response => response.data), + [diagnosekoderMedNavnResponses], + ); + + const focusAddButton = () => { + if (addButtonRef.current) { + addButtonRef.current.focus(); + } + }; + + const slettDiagnosekode = (diagnosekode: string) => + httpUtils.post( + endreDiagnosekoderLink.href, + { + behandlingUuid, + versjon, + diagnosekoder: diagnosekoder.filter(kode => kode !== diagnosekode), + }, + httpErrorHandler, + ); + + const lagreDiagnosekode = (nyeDiagnosekoder: string[]) => + httpUtils.post( + endreDiagnosekoderLink.href, + { + behandlingUuid, + versjon, + diagnosekoder: [...new Set([...diagnosekoder, ...nyeDiagnosekoder])], + }, + httpErrorHandler, + ); + + const slettDiagnosekodeMutation = useMutation((diagnosekode: string) => slettDiagnosekode(diagnosekode), { + onSuccess: () => { + refetch().finally(() => { + onDiagnosekoderUpdated(); + focusAddButton(); + }); + }, + }); + const lagreDiagnosekodeMutation = useMutation((nyeDiagnosekoder: string[]) => lagreDiagnosekode(nyeDiagnosekoder), { + onSuccess: () => { + refetch().finally(() => { + onDiagnosekoderUpdated(); + setModalIsOpen(false); + focusAddButton(); + }); + }, + }); + + return ( +
    + ( + ( + setModalIsOpen(true)} + ariaLabel="Legg til diagnosekode" + ref={addButtonRef} + /> + )} + /> + )} + > + Diagnosekoder + + {isLoading ? ( + + ) : ( + + {diagnosekoder.length === 0 && ( + } text="Ingen diagnosekode registrert." /> + )} + {diagnosekoder.length >= 1 && ( + + )} + + )} + setModalIsOpen(false)} + searcherPromise={diagnosekodeSearcherPromise} + /> +
    + ); +}; + +export default Diagnosekodeoversikt; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeoversikt/diagnosekodeoversikt.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeoversikt/diagnosekodeoversikt.css" new file mode 100644 index 0000000000..474bae0071 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/diagnosekodeoversikt/diagnosekodeoversikt.css" @@ -0,0 +1,33 @@ +.diagnosekodeoversikt__modal { + overflow: visible; + color: #262626; +} + +.diagnosekodeoversikt__modal form { + margin: 0; +} + +.diagnosekodeoversikt__modal :global .autocomplete ul { + /* + This overrides the position: relative set in the Autocomplete component css. + Without this, the autocomplete suggestions appear inline in the DOM, pushing + other content down, causing jank. + + Using fixed works with dialog element, let's the popup content overflow the + dialog box. + */ + position: fixed; + + /* + The css in Autocomplete component used in the modal has width: 100% (minus + a little) for the suggestions list. With the original position: relative + that makes the width of the suggestion list match the parent Autocomplete + input element. + + When changing to position fixed, the width: 100% becomes calculated from the + viewport instead, so the ul becomes much to wide. + + Setting width: inherit makes it as wide as its content instead. + */ + width: inherit; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-knapp/DokumentKnapp.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-knapp/DokumentKnapp.tsx" new file mode 100644 index 0000000000..b024dd2974 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-knapp/DokumentKnapp.tsx" @@ -0,0 +1,17 @@ +import { Link } from '@navikt/ds-react'; +import * as React from 'react'; +import { DocumentIcon } from '@navikt/ft-plattform-komponenter'; +import styles from './dokumentKnapp.css'; + +interface DokumentKnappProps { + href: string; +} + +const DokumentKnapp = ({ href }: DokumentKnappProps): JSX.Element => ( + + + Åpne dokument + +); + +export default DokumentKnapp; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-knapp/dokumentKnapp.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-knapp/dokumentKnapp.css" new file mode 100644 index 0000000000..d115b0cbf6 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-knapp/dokumentKnapp.css" @@ -0,0 +1,25 @@ +.dokumentKnapp { + padding: 0.375rem 0.5rem; + border-radius: 5px; + border: 1px solid #0067c5; + background: white; + text-decoration: none; +} + +.dokumentKnapp:hover { + color: white; + text-decoration: none; + background-color: #254b6d; +} + +.dokumentKnapp:hover svg { + fill: white; + stroke: white; +} + +.dokumentKnapp svg { + margin-right: 0.5rem; + width: 1.125rem; + height: 1.375rem; + stroke: none; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-link/DokumentLink.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-link/DokumentLink.tsx" new file mode 100644 index 0000000000..dcae41e143 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-link/DokumentLink.tsx" @@ -0,0 +1,28 @@ +import { prettifyDateString } from '@fpsak-frontend/utils'; +import { DocumentIcon } from '@navikt/ft-plattform-komponenter'; +import { Link } from '@navikt/ds-react'; +import React from 'react'; +import LinkRel from '../../../constants/LinkRel'; +import Dokument, { dokumentLabel } from '../../../types/Dokument'; +import { findLinkByRel } from '../../../util/linkUtils'; +import styles from './dokumentLink.css'; + +interface DokumentLinkProps { + dokument: Dokument; + etikett?: React.ReactNode; + visDokumentIkon?: boolean; +} + +const DokumentLink = ({ dokument, etikett, visDokumentIkon }: DokumentLinkProps): JSX.Element => { + const { type, datert, links } = dokument; + const dokumentLink = findLinkByRel(LinkRel.DOKUMENT_INNHOLD, links); + return ( + + {visDokumentIkon && } + {dokumentLabel[type]} {prettifyDateString(datert)} + {etikett &&
    {etikett}
    } + + ); +}; + +export default DokumentLink; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-link/dokumentLink.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-link/dokumentLink.css" new file mode 100644 index 0000000000..5eee39702c --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokument-link/dokumentLink.css" @@ -0,0 +1,18 @@ +.dokumentLink { + display: flex; +} + +.dokumentLink:hover { + text-decoration: none; +} + +.dokumentLink__etikett { + margin-left: 1rem; +} + +svg.dokumentLink__dokumentikon { + margin-right: 0.5rem; + width: 1.125rem; + height: 1.375rem; + stroke: none; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentasjon-footer/DokumentasjonFooter.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentasjon-footer/DokumentasjonFooter.tsx" new file mode 100644 index 0000000000..c4bb81f18b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentasjon-footer/DokumentasjonFooter.tsx" @@ -0,0 +1,22 @@ +import React from 'react'; +import styles from './dokumentasjonFooter.css'; + +interface DokumentasjonFooterProps { + firstSectionRenderer: () => React.ReactNode; + secondSectionRenderer: () => React.ReactNode; + thirdSectionRenderer: () => React.ReactNode; +} + +const DokumentasjonFooter = ({ + firstSectionRenderer, + secondSectionRenderer, + thirdSectionRenderer, +}: DokumentasjonFooterProps): JSX.Element => ( +
    +
    {firstSectionRenderer()}
    +
    {secondSectionRenderer()}
    +
    {thirdSectionRenderer()}
    +
    +); + +export default DokumentasjonFooter; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentasjon-footer/dokumentasjonFooter.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentasjon-footer/dokumentasjonFooter.css" new file mode 100644 index 0000000000..1cab3b1a5d --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentasjon-footer/dokumentasjonFooter.css" @@ -0,0 +1,6 @@ +.dokumentasjonFooter { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + column-gap: 4rem; + max-width: 1190px; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentdetaljer/Dokumentdetaljer.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentdetaljer/Dokumentdetaljer.tsx" new file mode 100644 index 0000000000..f3a9430285 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentdetaljer/Dokumentdetaljer.tsx" @@ -0,0 +1,46 @@ +import React from 'react'; +import LinkRel from '../../../constants/LinkRel'; +import Dokument, { Dokumenttype } from '../../../types/Dokument'; +import { findLinkByRel } from '../../../util/linkUtils'; +import StrukturerDokumentController from '../strukturer-dokument-controller/StrukturerDokumentController'; +import StrukturertDokumentDetaljer from '../strukturert-dokument-detaljer/StrukturertDokumentDetaljer'; + +interface DokumentdetaljerProps { + dokument: Dokument; + onChange: () => void; + editMode: boolean; + onEditClick: () => void; + strukturerteDokumenter: Dokument[]; +} + +const Dokumentdetaljer = ({ + dokument, + onChange, + editMode, + onEditClick, + strukturerteDokumenter, +}: DokumentdetaljerProps): JSX.Element => { + const { type, links } = dokument; + if (type === Dokumenttype.UKLASSIFISERT || editMode) { + const strukturerDokumentLink = findLinkByRel(LinkRel.ENDRE_DOKUMENT, links); + return ( + + ); + } + return ( + + ); +}; + +export default Dokumentdetaljer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentfilter/Dokumentfilter.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentfilter/Dokumentfilter.tsx" new file mode 100644 index 0000000000..667b6460bf --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentfilter/Dokumentfilter.tsx" @@ -0,0 +1,72 @@ +import { Collapse, Expand, FilterFilled } from '@navikt/ds-icons'; +import { Button, Checkbox, Label } from '@navikt/ds-react'; +import classNames from 'classnames'; +import React, { useState } from 'react'; +import OutsideClickHandler from 'react-outside-click-handler'; +import { dokumentLabel, Dokumenttype } from '../../../types/Dokument'; +import styles from './dokumentfilter.css'; + +interface ChevronWithTextProps { + chevronDirection: 'opp' | 'ned'; + onClick: () => void; + text: string; +} +interface DokumentfilterProps { + text: string; + className: string; + filters: Array; + onFilterChange: (value: string) => void; +} + +const ChevronWithText = ({ chevronDirection, onClick, text }: ChevronWithTextProps): JSX.Element => ( + +); + +const Dokumentfilter = ({ + text, + className, + filters, + onFilterChange: filtrerDokumenttype, +}: DokumentfilterProps): JSX.Element => { + const [open, setOpen] = useState(false); + const chevronDirection = open ? 'opp' : 'ned'; + const dokumenttypeListe = [...Object.values(Dokumenttype)]; + const listeErFiltrert = filters.length < 4; + return ( +
    + + setOpen(!open)} text={text} /> + + + {open && ( + setOpen(false)}> +
    + + setOpen(!open)} text={text} /> + +
    + {dokumenttypeListe.map(type => ( + filtrerDokumenttype(type)}> + {dokumentLabel[type]} + + ))} +
    +
    +
    +
    + )} +
    + ); +}; + +export default Dokumentfilter; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentfilter/dokumentfilter.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentfilter/dokumentfilter.css" new file mode 100644 index 0000000000..f4d33b42ce --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentfilter/dokumentfilter.css" @@ -0,0 +1,36 @@ +.dokumentfilter { + margin-right: -2.8125rem; + margin-top: -0.25rem; +} + +.chevronDropdown__dropdown { + position: absolute; + top: -14px; + left: 40px; + border: 1px solid black; + border-radius: 3px; + padding: 13px 13px 24px 16px; + margin-left: 4px; + z-index: 1; + background-color: white; +} + +.chevronDropdown__dropdown__checkbox div { + margin: 10px 0; +} + +button.chevronDropdown__toggleButton { + border: none; + background: none; + padding: unset; + color: black; +} + +.chevronDropdown__toggleButton__text { + display: inline; + line-height: 19px; +} + +.chevronDropdown__hidden { + visibility: hidden; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentnavigasjon/Dokumentnavigasjon.spec.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentnavigasjon/Dokumentnavigasjon.spec.tsx" new file mode 100644 index 0000000000..033cc241d4 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentnavigasjon/Dokumentnavigasjon.spec.tsx" @@ -0,0 +1,95 @@ +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import Dokumentnavigasjon from './Dokumentnavigasjon'; +import mockedDokumentoversikt from '../../../../mock/mocked-data/mockedDokumentoversikt'; + +describe('Dokumentnavigasjon', () => { + const { dokumenter } = mockedDokumentoversikt; + + it('should render "Ingen dokumenter å vise" text when there are no documents to show', () => { + render( + null} + expandedByDefault + />, + ); + + expect(screen.getByText(/Ingen dokumenter å vise/i)).toBeInTheDocument(); + }); + + it('should render documents in list when expanded by default', async () => { + render( + null} + expandedByDefault + />, + ); + + expect(screen.getByText(/ikke klassifisert/i)).toBeInTheDocument(); + expect(screen.getByText(/andre med. oppl./i)).toBeInTheDocument(); + expect(screen.getByText(/ikke med. oppl./i)).toBeInTheDocument(); + }); + + it.skip('should show no documents when not expanded by default', async () => { + render( + null} + />, + ); + + expect(screen.queryByText(/ikke klassifisert/i)).not.toBeVisible(); + expect(screen.queryByText(/andre med. oppl./i)).not.toBeVisible(); + expect(screen.queryByText(/ikke med. oppl./i)).not.toBeVisible(); + }); + + test('documents are filtered correctly', async () => { + render( + null} + expandedByDefault + displayFilterOption + />, + ); + expect(screen.getByText(/ikke klassifisert/i)).toBeInTheDocument(); + + userEvent.click(screen.getByRole('button', { name: 'Type' })); + + const ikkeKlassifisertCheckbox = await screen.findByLabelText(/ikke klassifisert/i); + userEvent.click(ikkeKlassifisertCheckbox); + userEvent.click(screen.getAllByText(/type/i)[1]); + + await waitFor(() => expect(screen.queryByText(/ikke klassifisert/i)).not.toBeInTheDocument()); + }); + + test('dropdown filter opens and closes correctly when filter functionality is activated', async () => { + render( + null} + />, + ); + userEvent.click(screen.getByRole('button', { name: 'Type' })); + const ikkeKlassifisertCheckbox = await screen.findByLabelText(/ikke klassifisert/i); + expect(ikkeKlassifisertCheckbox).toBeTruthy(); + + userEvent.click(document.body); + await waitFor(() => expect(screen.queryByLabelText(/ikke klassifisert/i)).not.toBeInTheDocument()); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentnavigasjon/Dokumentnavigasjon.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentnavigasjon/Dokumentnavigasjon.tsx" new file mode 100644 index 0000000000..834e7b3df0 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentnavigasjon/Dokumentnavigasjon.tsx" @@ -0,0 +1,112 @@ +import { Accordion, BodyShort } from '@navikt/ds-react'; +import { InteractiveList } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import { Dokument, Dokumenttype } from '../../../types/Dokument'; +import Dokumentfilter from '../dokumentfilter/Dokumentfilter'; +import StrukturertDokumentElement from '../strukturet-dokument-element/StrukturertDokumentElement'; +import UstrukturertDokumentElement from '../ustrukturert-dokument-element/UstrukturertDokumentElement'; +import styles from './dokumentnavigasjon.css'; + +interface DokumentnavigasjonProps { + tittel: string; + dokumenter: Dokument[]; + onDokumentValgt: (dokument: Dokument) => void; + valgtDokument: Dokument; + expandedByDefault?: boolean; + displayFilterOption?: boolean; +} + +const erIkkeDuplikat = (dokument: Dokument) => dokument.duplikatAvId === null; +const lagDokumentelement = (dokument: Dokument) => ({ + dokument, + renderer: () => + dokument.type === Dokumenttype.UKLASSIFISERT ? ( + + ) : ( + + ), +}); + +const Dokumentnavigasjon = ({ + tittel, + dokumenter, + onDokumentValgt, + valgtDokument, + expandedByDefault, + displayFilterOption, +}: DokumentnavigasjonProps): JSX.Element => { + const [dokumenttypeFilter, setDokumenttypeFilter] = React.useState([...Object.values(Dokumenttype)]); + const updateDokumenttypeFilter = type => + dokumenttypeFilter.includes(type) + ? setDokumenttypeFilter(dokumenttypeFilter.filter(v => v !== type)) + : setDokumenttypeFilter(dokumenttypeFilter.concat([type])); + + const filtrerteDokumenter = dokumenter.filter( + dokument => dokumenttypeFilter.includes(dokument.type) && erIkkeDuplikat(dokument), + ); + + const dokumentElementer = filtrerteDokumenter.map(lagDokumentelement); + + return ( +
    + + + {tittel} + +
    +
    +
    + + Status + +
    + {!displayFilterOption && ( +
    + + Type + +
    + )} + {displayFilterOption && ( + + )} +
    + + Datert + +
    +
    + + Part + +
    +
    + {dokumentElementer.length === 0 && ( +
    + Ingen dokumenter å vise +
    + )} + dokumenttypeFilter.includes(element?.dokument?.type)) + .map((element, currentIndex) => ({ + content: element.renderer(), + active: element.dokument === valgtDokument, + key: `${currentIndex}`, + onClick: () => onDokumentValgt(element.dokument), + }))} + /> +
    +
    +
    +
    +
    + ); +}; + +export default Dokumentnavigasjon; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentnavigasjon/dokumentnavigasjon.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentnavigasjon/dokumentnavigasjon.css" new file mode 100644 index 0000000000..54283d5d38 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentnavigasjon/dokumentnavigasjon.css" @@ -0,0 +1,63 @@ +.dokumentnavigasjon { + min-width: 28.5rem; + + /* important pga https://github.com/webpack-contrib/mini-css-extract-plugin/issues/188 */ +} + +.dokumentnavigasjon__heading { + padding: 1.25rem 0.9375rem 0; +} + +.dokumentnavigasjon__container { + flex-grow: 1; +} + +.dokumentnavigasjon__columnHeadings { + position: relative; + display: flex; + margin-bottom: 0.75rem; +} + +.dokumentnavigasjon__columnHeading--first { + margin-left: 0.875rem; +} + +.dokumentnavigasjon__columnHeading--second { + margin-left: 1.375rem; +} + +.dokumentnavigasjon__columnHeading--third { + margin-left: 9.5rem; +} + +.dokumentnavigasjon__columnHeading--fourth { + margin-left: 3.125rem; +} + +.dokumentnavigasjon :global(.navds-accordion__content) { + border: 1px solid #6a6a6a; + border-radius: 4px; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-top: none; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; +} + +.dokumentnavigasjon :global(.navds-accordion__header) { + border: 1px solid #6a6a6a; + border-bottom: none; + border-radius: 4px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.dokumentnavigasjon :global(.navds-accordion__header[aria-expanded='false']) { + border-bottom: 1px solid #6a6a6a; + border-radius: 4px; +} + +.dokumentnavigasjon :global(.navds-accordion__header[aria-expanded='true']) { + box-shadow: none; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentoversikt-messages/DokmentoversiktMessages.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentoversikt-messages/DokmentoversiktMessages.tsx" new file mode 100644 index 0000000000..a3090e77c9 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/dokumentoversikt-messages/DokmentoversiktMessages.tsx" @@ -0,0 +1,106 @@ +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import { Alert, Button } from '@navikt/ds-react'; +import React from 'react'; +import FagsakYtelseType from '../../../constants/FagsakYtelseType'; +import Dokumentoversikt from '../../../types/Dokumentoversikt'; +import ContainerContext from '../../context/ContainerContext'; +import FristForDokumentasjonUtløptPanel from '../frist-for-dokumentasjon-utløpt-panel/FristForDokumentasjonUtløptPanel'; + +interface DokumentoversiktMessagesProps { + dokumentoversikt: Dokumentoversikt; + harRegistrertDiagnosekode: boolean; + kanNavigereVidere: boolean; + navigerTilNesteSteg: () => void; +} + +const DokumentoversiktMessages = ({ + dokumentoversikt, + harRegistrertDiagnosekode, + kanNavigereVidere, + navigerTilNesteSteg, +}: DokumentoversiktMessagesProps): JSX.Element => { + const { onFinished, readOnly, fagsakYtelseType } = React.useContext(ContainerContext); + if (!dokumentoversikt) { + return null; + } + const { ustrukturerteDokumenter } = dokumentoversikt; + + const visFristForDokumentasjonUtløptMelding = + ustrukturerteDokumenter.length === 0 && !dokumentoversikt.harGyldigSignatur(); + + const visHåndterNyeDokumenterMelding = + !dokumentoversikt.harGyldigSignatur() && dokumentoversikt.harDokumenter() && !visFristForDokumentasjonUtløptMelding; + + const erPleiepengerSluttfaseFagsak = fagsakYtelseType === FagsakYtelseType.PLEIEPENGER_SLUTTFASE; + + return ( + <> + {harRegistrertDiagnosekode === false && ( + + + Diagnosekode mangler. Du må legge til en diagnosekode for å vurdere tilsyn og pleie. + + + )} + {visFristForDokumentasjonUtløptMelding && !readOnly && ( + <> + + + {erPleiepengerSluttfaseFagsak ? ( + <>Dokumentasjon signert av lege eller helseinstitusjon mangler. + ) : ( + <>Dokumentasjon signert av sykehuslege/spesialisthelsetjenesten mangler. + )} + Sett saken på vent mens du innhenter mer dokumentasjon. + + + + onFinished({ ikkeVentPåGodkjentLegeerklæring: true })} + /> + + + )} + {visHåndterNyeDokumenterMelding && fagsakYtelseType !== FagsakYtelseType.PLEIEPENGER_SLUTTFASE && ( + + + Dokumentasjon signert av sykehuslege/spesialisthelsetjenesten mangler. Håndter eventuelle nye dokumenter, + eller sett saken på vent mens du innhenter mer dokumentasjon. + + + )} + {dokumentoversikt.harDokumenter() === false && ( + + Ingen dokumenter å vise + + )} + {kanNavigereVidere && !readOnly && ( + + +
    + {erPleiepengerSluttfaseFagsak ? ( + <>Dokumentasjon av livets sluttfase er ferdig vurdert og du kan gå videre i vurderingen. + ) : ( + <>Dokumentasjon av sykdom er ferdig vurdert og du kan gå videre i vurderingen. + )} + +
    +
    +
    + )} + + ); +}; + +export default DokumentoversiktMessages; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/duplikat-radiobuttons/DuplikatRadiobuttons.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/duplikat-radiobuttons/DuplikatRadiobuttons.tsx" new file mode 100644 index 0000000000..82410a1f00 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/duplikat-radiobuttons/DuplikatRadiobuttons.tsx" @@ -0,0 +1,73 @@ +import { prettifyDateString } from '@fpsak-frontend/utils'; +import { RadioGroupPanelRHF } from '@fpsak-frontend/form'; +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import { Link } from '@navikt/ds-react'; +import * as React from 'react'; +import { useFormContext } from 'react-hook-form'; +import LinkRel from '../../../constants/LinkRel'; +import Dokument, { dokumentLabel } from '../../../types/Dokument'; +import { StrukturerDokumentFormFieldName as FieldName } from '../../../types/StrukturerDokumentFormState'; +import { findLinkByRel } from '../../../util/linkUtils'; +import ContainerContext from '../../context/ContainerContext'; +import { required } from '../../form/validators'; + +export const ikkeDuplikatValue = 'ikkeDuplikat'; + +interface DuplikatRadiobuttonsProps { + dokument: Dokument; + strukturerteDokumenter: Dokument[]; +} + +const DuplikatRadiobuttons = ({ dokument, strukturerteDokumenter }: DuplikatRadiobuttonsProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + const formMethods = useFormContext(); + const { watch } = formMethods; + const dokumenttype = watch(FieldName.INNEHOLDER_MEDISINSKE_OPPLYSNINGER); + const dokumentDatert = watch(FieldName.DATERT); + + const potensielleDuplikater = strukturerteDokumenter.filter( + ({ datert, type, id, duplikatAvId }) => + datert === dokumentDatert && type === dokumenttype && id !== dokument.id && duplikatAvId == null, + ); + + const harPotensielleDuplikater = potensielleDuplikater.length > 0; + const getDuplikatRadios = () => { + const radios = potensielleDuplikater.map(potensiellDuplikat => { + const dokumentLink = findLinkByRel(LinkRel.DOKUMENT_INNHOLD, potensiellDuplikat.links); + return { + label: ( + + {`${dokumentLabel[potensiellDuplikat.type]} - ${prettifyDateString(potensiellDuplikat.datert)}`} + + ), + value: potensiellDuplikat.id, + }; + }); + radios.push({ label: Dokumentet er ikke et duplikat, value: ikkeDuplikatValue }); + return radios; + }; + + if (!harPotensielleDuplikater) { + return null; + } + + return ( + + + Det finnes ett eller flere dokumenter datert til samme dato. +
    + Velg om dette dokumentet er et duplikat av ett av følgende dokumenter: + + } + radios={getDuplikatRadios()} + validators={{ required }} + /> +
    + ); +}; + +export default DuplikatRadiobuttons; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/duplikatliste/Duplikatliste.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/duplikatliste/Duplikatliste.tsx" new file mode 100644 index 0000000000..3937e322dd --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/duplikatliste/Duplikatliste.tsx" @@ -0,0 +1,63 @@ +import { prettifyDateString } from '@fpsak-frontend/utils'; +import { BucketIcon } from '@navikt/ft-plattform-komponenter'; +import { Link } from '@navikt/ds-react'; +import React, { useState } from 'react'; +import LinkRel from '../../../constants/LinkRel'; +import Dokument, { dokumentLabel } from '../../../types/Dokument'; +import { findLinkByRel } from '../../../util/linkUtils'; +import SlettDuplikatModal from '../slett-duplikat-modal/SlettDuplikatModal'; +import WriteAccessBoundContent from '../write-access-bound-content/WriteAccessBoundContent'; +import styles from './duplikatliste.css'; + +interface DuplikatlisteProps { + dokumenter: Dokument[]; + onRemoveDuplikat: () => void; +} + +const Duplikatliste = ({ dokumenter, onRemoveDuplikat }: DuplikatlisteProps): JSX.Element => { + const [isModalOpen, setModalIsOpen] = useState(false); + const [selectedDocument, setSelectedDocument] = useState(null); + + return ( + <> +
      + {dokumenter.map(dokument => { + const dokumentLink = findLinkByRel(LinkRel.DOKUMENT_INNHOLD, dokument.links); + return ( +
    • + + {`${dokumentLabel[dokument.type]} - ${prettifyDateString(dokument.datert)}`} + + ( + + )} + /> +
    • + ); + })} +
    + {isModalOpen && ( + { + setModalIsOpen(false); + onRemoveDuplikat(); + }} + handleCloseModal={() => setModalIsOpen(false)} + selectedDocument={selectedDocument} + /> + )} + + ); +}; +export default Duplikatliste; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/duplikatliste/duplikatliste.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/duplikatliste/duplikatliste.css" new file mode 100644 index 0000000000..41cd27c9af --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/duplikatliste/duplikatliste.css" @@ -0,0 +1,43 @@ +.dokumentliste { + list-style-type: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; +} + +.dokumentliste__element { + align-items: center; + display: flex; + position: relative; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.dokumentliste__element:not(:first-child) { + margin-top: 0.5rem; +} + +.dokumentliste__deleteButton { + background: none; + border: none; + cursor: pointer; + height: 1.4375rem; + margin-left: 1.25rem; + padding: 0; + display: flex; + align-items: center; +} + +.dokumentliste__deleteButtonText { + text-decoration: underline; + color: #0067c5; + margin-left: 0.5rem; +} + +.dokumentliste__beskrivelse { + margin: 0; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/endre-vurdering-controller/EndreVurderingController.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/endre-vurdering-controller/EndreVurderingController.tsx" new file mode 100644 index 0000000000..e7ee661f01 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/endre-vurdering-controller/EndreVurderingController.tsx" @@ -0,0 +1,194 @@ +import { httpUtils, Period } from '@fpsak-frontend/utils'; +import { Box, Margin, PageContainer } from '@navikt/ft-plattform-komponenter'; +import React, { useMemo } from 'react'; +import Dokument from '../../../types/Dokument'; +import Link from '../../../types/Link'; +import { Vurderingsversjon } from '../../../types/Vurdering'; +import { PeriodeMedEndring, PerioderMedEndringResponse } from '../../../types/PeriodeMedEndring'; +import OverlappendePeriodeModal from '../overlappende-periode-modal/OverlappendePeriodeModal'; +import ActionType from './actionTypes'; +import vurderingControllerReducer from './reducer'; +import { postEndreVurdering, postEndreVurderingDryRun } from '../../../api/api'; +import ContainerContext from '../../context/ContainerContext'; +import scrollUp from '../../../util/viewUtils'; +import LagreVurderingFeiletMelding from '../lagre-vurdering-feilet-melding/LagreVurderingFeiletMelding'; + +interface EndreVurderingControllerProps { + endreVurderingLink: Link; + dataTilVurderingUrl: string; + onVurderingLagret: () => void; + formRenderer: ( + dokumenter: Dokument[], + onSubmit: (vurderingsversjon: Vurderingsversjon) => void, + isSubmitting: boolean, + ) => React.ReactNode; + vurderingsid: string; + vurderingsversjonId: string; +} + +const EndreVurderingController = ({ + endreVurderingLink, + dataTilVurderingUrl, + onVurderingLagret, + formRenderer, + vurderingsid, + vurderingsversjonId, +}: EndreVurderingControllerProps): JSX.Element => { + const { httpErrorHandler } = React.useContext(ContainerContext); + + const [state, dispatch] = React.useReducer(vurderingControllerReducer, { + sjekkForEksisterendeVurderingerPågår: false, + lagringAvVurderingPågår: false, + lagreVurderingHarFeilet: false, + hentDataTilVurderingPågår: true, + hentDataTilVurderingHarFeilet: false, + dokumenter: [], + perioderMedEndring: [], + overlappendePeriodeModalOpen: false, + vurderingsversjonTilLagringFraModal: null, + }); + + const { + sjekkForEksisterendeVurderingerPågår, + lagringAvVurderingPågår, + lagreVurderingHarFeilet, + hentDataTilVurderingPågår, + hentDataTilVurderingHarFeilet, + dokumenter, + perioderMedEndring, + overlappendePeriodeModalOpen, + vurderingsversjonTilLagringFraModal, + } = state; + + const controller = useMemo(() => new AbortController(), []); + + function endreVurdering(nyVurderingsversjon: Partial) { + dispatch({ type: ActionType.LAGRING_AV_VURDERING_PÅBEGYNT }); + return postEndreVurdering( + endreVurderingLink.href, + endreVurderingLink.requestPayload.behandlingUuid, + vurderingsid, + nyVurderingsversjon, + httpErrorHandler, + controller.signal, + ).then( + () => { + onVurderingLagret(); + dispatch({ type: ActionType.VURDERING_LAGRET }); + scrollUp(); + }, + () => { + dispatch({ type: ActionType.LAGRE_VURDERING_FEILET }); + scrollUp(); + }, + ); + } + + const sjekkForEksisterendeVurderinger = ( + nyVurderingsversjon: Vurderingsversjon, + ): Promise => + postEndreVurderingDryRun( + endreVurderingLink.href, + endreVurderingLink.requestPayload.behandlingUuid, + vurderingsid, + nyVurderingsversjon, + httpErrorHandler, + controller.signal, + ); + + const advarOmEksisterendeVurderinger = ( + nyVurderingsversjon: Vurderingsversjon, + perioderMedEndringValue: PeriodeMedEndring[], + ) => { + dispatch({ + type: ActionType.ADVAR_OM_EKSISTERENDE_VURDERINGER, + perioderMedEndring: perioderMedEndringValue, + vurderingsversjonTilLagringFraModal: nyVurderingsversjon, + }); + }; + + const initializePerioderMedEndringer = (perioderMedEndringResponse: PerioderMedEndringResponse) => + perioderMedEndringResponse.perioderMedEndringer.map(({ periode: { fom, tom }, ...otherFields }) => ({ + periode: new Period(fom, tom), + ...otherFields, + })); + + const beOmBekreftelseFørLagringHvisNødvendig = (nyVurderingsversjon: Vurderingsversjon) => { + dispatch({ type: ActionType.SJEKK_FOR_EKSISTERENDE_VURDERINGER_PÅBEGYNT }); + const nyVurderingsversjonMedVersjonId = { ...nyVurderingsversjon, versjon: vurderingsversjonId }; + sjekkForEksisterendeVurderinger(nyVurderingsversjonMedVersjonId).then( + perioderMedEndringerResponse => { + const perioderMedEndringer = initializePerioderMedEndringer(perioderMedEndringerResponse); + const harOverlappendePerioder = perioderMedEndringer?.length > 0; + if (harOverlappendePerioder) { + advarOmEksisterendeVurderinger(nyVurderingsversjonMedVersjonId, perioderMedEndringer); + } else { + endreVurdering(nyVurderingsversjonMedVersjonId); + } + }, + () => { + dispatch({ type: ActionType.LAGRE_VURDERING_FEILET }); + }, + ); + }; + + function hentDataTilVurdering(): Promise { + if (!dataTilVurderingUrl) { + return new Promise(resolve => { + resolve([]); + }); + } + return httpUtils.get(dataTilVurderingUrl, httpErrorHandler, { signal: controller.signal }); + } + + const handleHentDataTilVurderingError = () => { + dispatch({ type: ActionType.HENT_DATA_TIL_VURDERING_HAR_FEILET }); + }; + + React.useEffect(() => { + let isMounted = true; + dispatch({ type: ActionType.HENT_DATA_TIL_VURDERING }); + hentDataTilVurdering() + .then((dokumenterResponse: Dokument[]) => { + if (isMounted) { + dispatch({ type: ActionType.HENTET_DATA_TIL_VURDERING, dokumenter: dokumenterResponse }); + } + }) + .catch(handleHentDataTilVurderingError); + + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + const isSubmitting = lagringAvVurderingPågår || sjekkForEksisterendeVurderingerPågår; + return ( + + {lagreVurderingHarFeilet && ( + + + + )} + {formRenderer(dokumenter, beOmBekreftelseFørLagringHvisNødvendig, isSubmitting)} + {lagreVurderingHarFeilet && ( + + + + )} + dispatch({ type: ActionType.LAGRING_AV_VURDERING_AVBRUTT })} + onConfirm={() => { + endreVurdering(vurderingsversjonTilLagringFraModal).then(() => { + dispatch({ type: ActionType.VURDERING_LAGRET, perioderMedEndring }); + }); + }} + isOpen={overlappendePeriodeModalOpen} + isSubmitting={lagringAvVurderingPågår} + /> + + ); +}; + +export default EndreVurderingController; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/endre-vurdering-controller/actionTypes.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/endre-vurdering-controller/actionTypes.ts" new file mode 100644 index 0000000000..c2bf5e9f10 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/endre-vurdering-controller/actionTypes.ts" @@ -0,0 +1,13 @@ +enum ActionType { + SJEKK_FOR_EKSISTERENDE_VURDERINGER_PÅBEGYNT = 'sjekkForEksisterendeVurderingerPåbegynt', + LAGRING_AV_VURDERING_PÅBEGYNT = 'lagringAvVurderingPåbegynt', + VURDERING_LAGRET = 'vurderingLagret', + LAGRE_VURDERING_FEILET = 'lagreVurderingFeilet', + LAGRING_AV_VURDERING_AVBRUTT = 'lagringAvVurderingAvbrutt', + ADVAR_OM_EKSISTERENDE_VURDERINGER = 'advarOmEksisterendeVurderinger', + HENT_DATA_TIL_VURDERING = 'hentDataTilVurdering', + HENTET_DATA_TIL_VURDERING = 'hentetDataTilVurdering', + HENT_DATA_TIL_VURDERING_HAR_FEILET = 'hentDataTilVurderingHarFeilet', +} + +export default ActionType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/endre-vurdering-controller/reducer.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/endre-vurdering-controller/reducer.ts" new file mode 100644 index 0000000000..6e716e2ded --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/endre-vurdering-controller/reducer.ts" @@ -0,0 +1,92 @@ +import ActionType from './actionTypes'; +import Dokument from '../../../types/Dokument'; +import { PeriodeMedEndring } from '../../../types/PeriodeMedEndring'; +import { Vurderingsversjon } from '../../../types/Vurdering'; +import { dokumentSorter } from '../../../util/dokumentUtils'; + +interface State { + sjekkForEksisterendeVurderingerPågår: boolean; + lagringAvVurderingPågår: boolean; + lagreVurderingHarFeilet: boolean; + hentDataTilVurderingPågår: boolean; + hentDataTilVurderingHarFeilet: boolean; + dokumenter: Dokument[]; + perioderMedEndring: PeriodeMedEndring[]; + overlappendePeriodeModalOpen: boolean; + vurderingsversjonTilLagringFraModal: Vurderingsversjon | null; +} + +interface Action { + type: ActionType; + perioderMedEndring?: PeriodeMedEndring[]; + dokumenter?: Dokument[]; + vurderingsversjonTilLagringFraModal?: Vurderingsversjon; +} + +const vurderingControllerReducer = (state: State, action: Action): State => { + switch (action.type) { + case ActionType.SJEKK_FOR_EKSISTERENDE_VURDERINGER_PÅBEGYNT: + return { + ...state, + sjekkForEksisterendeVurderingerPågår: true, + }; + case ActionType.LAGRING_AV_VURDERING_PÅBEGYNT: + return { + ...state, + lagringAvVurderingPågår: true, + lagreVurderingHarFeilet: false, + }; + case ActionType.VURDERING_LAGRET: + return { + ...state, + lagringAvVurderingPågår: false, + perioderMedEndring: null, + overlappendePeriodeModalOpen: false, + }; + case ActionType.LAGRE_VURDERING_FEILET: + return { + ...state, + lagringAvVurderingPågår: false, + lagreVurderingHarFeilet: true, + sjekkForEksisterendeVurderingerPågår: false, + }; + case ActionType.LAGRING_AV_VURDERING_AVBRUTT: + return { + ...state, + overlappendePeriodeModalOpen: false, + vurderingsversjonTilLagringFraModal: null, + }; + case ActionType.ADVAR_OM_EKSISTERENDE_VURDERINGER: + return { + ...state, + overlappendePeriodeModalOpen: true, + sjekkForEksisterendeVurderingerPågår: false, + perioderMedEndring: action.perioderMedEndring, + vurderingsversjonTilLagringFraModal: action.vurderingsversjonTilLagringFraModal, + }; + case ActionType.HENT_DATA_TIL_VURDERING: + return { + ...state, + hentDataTilVurderingPågår: true, + hentDataTilVurderingHarFeilet: false, + }; + case ActionType.HENTET_DATA_TIL_VURDERING: { + const dokumenter = action.dokumenter?.sort(dokumentSorter); + return { + ...state, + dokumenter, + hentDataTilVurderingPågår: false, + }; + } + case ActionType.HENT_DATA_TIL_VURDERING_HAR_FEILET: + return { + ...state, + hentDataTilVurderingPågår: false, + hentDataTilVurderingHarFeilet: true, + }; + default: + return state; + } +}; + +export default vurderingControllerReducer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/frist-for-dokumentasjon-utl\303\270pt-panel/FristForDokumentasjonUtl\303\270ptPanel.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/frist-for-dokumentasjon-utl\303\270pt-panel/FristForDokumentasjonUtl\303\270ptPanel.tsx" new file mode 100644 index 0000000000..86b4c195dc --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/frist-for-dokumentasjon-utl\303\270pt-panel/FristForDokumentasjonUtl\303\270ptPanel.tsx" @@ -0,0 +1,41 @@ +import { BodyShort, Button, Checkbox } from '@navikt/ds-react'; +import { Box, InfoPanel, Margin } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import styles from './fristForDokumentasjonUtløptPanel.css'; + +interface FristForDokumentasjonUtløptPanelProps { + onProceedClick: () => void; +} + +const FristForDokumentasjonUtløptPanel = ({ onProceedClick }: FristForDokumentasjonUtløptPanelProps): JSX.Element => { + const [fristenErUtløpt, setFristenErUtløpt] = React.useState(false); + return ( + + + Dersom du ikke får dokumentasjon innen fristen, kan du avslå vilkåret og gå videre til vedtaksbrev. + +
    + + setFristenErUtløpt(!fristenErUtløpt)} + > + Legeerklæring fra sykehus/spesialisthelsetjenesten etter §9-16 første ledd er ikke mottatt innen fristen + + + {fristenErUtløpt === true && ( + + )} +
    +
    + ); +}; + +export default FristForDokumentasjonUtløptPanel; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/frist-for-dokumentasjon-utl\303\270pt-panel/fristForDokumentasjonUtl\303\270ptPanel.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/frist-for-dokumentasjon-utl\303\270pt-panel/fristForDokumentasjonUtl\303\270ptPanel.css" new file mode 100644 index 0000000000..efa40aa224 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/frist-for-dokumentasjon-utl\303\270pt-panel/fristForDokumentasjonUtl\303\270ptPanel.css" @@ -0,0 +1,13 @@ +.fristForDokumentasjonUtløptPanel { + border: 2px solid #ff9100; +} + +.fristForDokumentasjonUtløptPanel__formContainer { + display: flex; +} + +.fristForDokumentasjonUtløptPanel__formContainer__gåVidereKnapp { + position: relative; + top: 6px; + left: 16px; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/icon-with-text/IconWithText.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/icon-with-text/IconWithText.tsx" new file mode 100644 index 0000000000..077b010dd0 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/icon-with-text/IconWithText.tsx" @@ -0,0 +1,16 @@ +import React from 'react'; +import styles from './iconWithText.css'; + +interface IconWithTextProps { + iconRenderer: () => React.ReactNode; + text: string; +} + +const IconWithText = ({ text, iconRenderer }: IconWithTextProps): JSX.Element => ( +
    + {iconRenderer()} + {text} +
    +); + +export default IconWithText; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/icon-with-text/iconWithText.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/icon-with-text/iconWithText.css" new file mode 100644 index 0000000000..2f9d576f0c --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/icon-with-text/iconWithText.css" @@ -0,0 +1,7 @@ +.iconWithText { + display: flex; +} + +.iconWithText__text { + margin-left: 0.5rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ingen-perioder-\303\245-vurdere-melding/IngenPerioder\303\205VurdereMelding.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ingen-perioder-\303\245-vurdere-melding/IngenPerioder\303\205VurdereMelding.tsx" new file mode 100644 index 0000000000..e28a2243c1 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ingen-perioder-\303\245-vurdere-melding/IngenPerioder\303\205VurdereMelding.tsx" @@ -0,0 +1,10 @@ +import React from 'react'; +import { Alert } from '@navikt/ds-react'; + +const IngenPerioderÅVurdereMelding = (): JSX.Element => ( + + Ingen perioder å vurdere + +); + +export default IngenPerioderÅVurdereMelding; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiode-ikon-over-ikkeoppfylt/InnleggelsesperiodeIkonOverIkkeOppfylt.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiode-ikon-over-ikkeoppfylt/InnleggelsesperiodeIkonOverIkkeOppfylt.tsx" new file mode 100644 index 0000000000..ab476b013c --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiode-ikon-over-ikkeoppfylt/InnleggelsesperiodeIkonOverIkkeOppfylt.tsx" @@ -0,0 +1,8 @@ +import { IndicatorWithOverlay, InstitutionIcon, RedCrossIconFilled } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; + +const InnleggelsesperiodeIkonOverIkkeOppfylt = (): JSX.Element => ( + } overlayRenderer={() => } /> +); + +export default InnleggelsesperiodeIkonOverIkkeOppfylt; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiode-ikon-over-oppfylt/InnleggelsesperiodeIkonOverOppfylt.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiode-ikon-over-oppfylt/InnleggelsesperiodeIkonOverOppfylt.tsx" new file mode 100644 index 0000000000..abab2a96b3 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiode-ikon-over-oppfylt/InnleggelsesperiodeIkonOverOppfylt.tsx" @@ -0,0 +1,11 @@ +import { IndicatorWithOverlay, InstitutionIcon, GreenCheckIconFilled } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; + +const InnleggelsesperiodeIkonOverOppfylt = (): JSX.Element => ( + } + overlayRenderer={() => } + /> +); + +export default InnleggelsesperiodeIkonOverOppfylt; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/InnleggelsesperiodeFormModal.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/InnleggelsesperiodeFormModal.tsx" new file mode 100644 index 0000000000..43a2bbba79 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/InnleggelsesperiodeFormModal.tsx" @@ -0,0 +1,188 @@ +import { Alert, Button, Label, Modal } from '@navikt/ds-react'; +import { Box, Form, Margin } from '@navikt/ft-plattform-komponenter'; +import { PeriodpickerListRHF } from '@fpsak-frontend/form'; +import { Period } from '@fpsak-frontend/utils'; +import React from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { InnleggelsesperiodeDryRunResponse } from '../../../api/api'; +import AddButton from '../add-button/AddButton'; +import DeleteButton from '../delete-button/DeleteButton'; +import styles from './innleggelsesperiodeFormModal.css'; + +export enum FieldName { + INNLEGGELSESPERIODER = 'innleggelsesperioder', +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyType = any; + +interface InnleggelsesperiodeFormModal { + defaultValues: { + [FieldName.INNLEGGELSESPERIODER]: Period[]; + }; + setModalIsOpen: (isOpen: boolean) => void; + onSubmit: (formState) => void; + isLoading: boolean; + endringerPåvirkerAndreBehandlinger: (innleggelsesperioder: Period[]) => Promise; +} + +const InnleggelsesperiodeFormModal = ({ + defaultValues, + setModalIsOpen, + onSubmit, + isLoading, + endringerPåvirkerAndreBehandlinger, +}: InnleggelsesperiodeFormModal): JSX.Element => { + const formMethods = useForm({ + defaultValues: { + [FieldName.INNLEGGELSESPERIODER]: defaultValues[FieldName.INNLEGGELSESPERIODER].map(innleggelsesPeriode => ({ + period: innleggelsesPeriode, + })), + }, + }); + + const { + formState: { isDirty }, + getValues, + } = formMethods; + + const [showWarningMessage, setShowWarningMessage] = React.useState(false); + + const handleSubmit = formState => { + onSubmit(formState); + setModalIsOpen(false); + setShowWarningMessage(false); + }; + + const handleCloseModal = () => { + // eslint-disable-next-line no-alert + if ((isDirty && window.confirm('Du vil miste alle endringer du har gjort')) || !isDirty) { + setModalIsOpen(false); + setShowWarningMessage(false); + } + }; + + return ( + + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + +
    + + { + const initialiserteInnleggelsesperioder = getValues().innleggelsesperioder.map( + ({ period }: AnyType) => new Period(period.fom, period.tom), + ); + const erAllePerioderGyldige = initialiserteInnleggelsesperioder.every(periode => periode.isValid()); + if (erAllePerioderGyldige) { + endringerPåvirkerAndreBehandlinger(initialiserteInnleggelsesperioder).then( + ({ førerTilRevurdering }) => setShowWarningMessage(førerTilRevurdering), + ); + } + }} + defaultValues={defaultValues[FieldName.INNLEGGELSESPERIODER] || []} + validators={{ + overlaps: (periodValue: Period) => { + const innleggelsesperioderFormValue = getValues() + .innleggelsesperioder.filter((periodWrapper: AnyType) => periodWrapper.period !== periodValue) + .map(({ period }: AnyType) => new Period(period.fom, period.tom)); + const { fom, tom } = periodValue; + const period = new Period(fom, tom); + if (period.overlapsWithSomePeriodInList(innleggelsesperioderFormValue)) { + return 'Innleggelsesperiodene kan ikke overlappe'; + } + return null; + }, + hasEmptyPeriodInputs: (periodValue: Period) => { + const { fom, tom } = periodValue; + if (!fom) { + return 'Fra-dato er påkrevd'; + } + if (!tom) { + return 'Til-dato er påkrevd'; + } + return null; + }, + fomIsBeforeOrSameAsTom: (periodValue: Period) => { + const { fom, tom } = periodValue; + const period = new Period(fom, tom); + + if (period.fomIsBeforeOrSameAsTom() === false) { + return 'Fra-dato må være tidligere eller samme som til-dato'; + } + return null; + }, + }} + renderBeforeFieldArray={fieldArrayMethods => ( + <> + + fieldArrayMethods.append({ fom: '', tom: '' })} + id="leggTilInnleggelsesperiodeKnapp" + /> + + +
    + + +
    +
    + + )} + renderContentAfterElement={(index, numberOfItems, fieldArrayMethods) => ( + fieldArrayMethods.remove(index)} /> + )} + /> + {showWarningMessage && ( + + + Endringene du har gjort på innleggelsesperiodene vil føre til en ny revurdering av en annen + behandling. Påvirker alle søkere. + + + )} +
    + +
    + + +
    +
    +
    +
    +
    +
    + ); +}; +export default InnleggelsesperiodeFormModal; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/innleggelsesperiodeFormModal.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/innleggelsesperiodeFormModal.css" new file mode 100644 index 0000000000..8efd316011 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/innleggelsesperiodeFormModal.css" @@ -0,0 +1,27 @@ +.innleggelsesperiodeFormModal { + overflow: visible; + max-width: 525px; + color: #262626; +} + +.innleggelsesperiodeFormModal form { + margin: 0; +} + +.innleggelsesperiodeFormModal legend { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} + +.innleggelsesperiodeFormModal__pickerLabels { + display: flex; +} + +.innleggelsesperiodeFormModal__firstLabel { + width: 218px; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/ny-label/NyLabel.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/ny-label/NyLabel.tsx" new file mode 100644 index 0000000000..a96dafe211 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/ny-label/NyLabel.tsx" @@ -0,0 +1,13 @@ +import { ContentWithTooltip } from '@navikt/ft-plattform-komponenter'; +import * as React from 'react'; +import styles from './nyLabel.css'; + +const NyLabel = (): JSX.Element => ( +
    + +
    Ny
    +
    +
    +); + +export default NyLabel; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/ny-label/nyLabel.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/ny-label/nyLabel.css" new file mode 100644 index 0000000000..810ac3dd6d --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/ny-label/nyLabel.css" @@ -0,0 +1,16 @@ +.nyLabel__container { + align-self: center; + margin-left: 0.5rem; +} + +.nyLabel__icon { + width: 25px; + height: 25px; + border-radius: 50%; + background: #0067c5; + color: white; + font-size: 0.875rem; + display: flex; + justify-content: center; + align-items: center; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/slettet-label/SlettetLabel.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/slettet-label/SlettetLabel.tsx" new file mode 100644 index 0000000000..32fa10fc5b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/slettet-label/SlettetLabel.tsx" @@ -0,0 +1,13 @@ +import { ContentWithTooltip, ExclamationMarkIcon } from '@navikt/ft-plattform-komponenter'; +import * as React from 'react'; +import styles from './slettetLabel.css'; + +const SlettetLabel = (): JSX.Element => ( +
    + + + +
    +); + +export default SlettetLabel; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/slettet-label/slettetLabel.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/slettet-label/slettetLabel.css" new file mode 100644 index 0000000000..6490b3c163 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeFormModal/slettet-label/slettetLabel.css" @@ -0,0 +1,4 @@ +.slettetLabel__container { + margin-left: 0.5rem; + align-self: center; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeliste/Innleggelsesperiodeliste.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeliste/Innleggelsesperiodeliste.tsx" new file mode 100644 index 0000000000..636f9c1b45 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeliste/Innleggelsesperiodeliste.tsx" @@ -0,0 +1,22 @@ +import React from 'react'; +import { Period, sortPeriodsByFomDate } from '@fpsak-frontend/utils'; +import styles from './innleggelsesperiodeliste.css'; + +interface InnleggelsesperiodelisteProps { + innleggelsesperioder: Period[]; +} + +const Innleggelsesperiodeliste = ({ innleggelsesperioder }: InnleggelsesperiodelisteProps): JSX.Element => ( +
      + {innleggelsesperioder.sort(sortPeriodsByFomDate).map(innleggelsesperiode => { + const { fom, tom } = innleggelsesperiode; + return ( +
    • + {innleggelsesperiode.prettifyPeriod()} +
    • + ); + })} +
    +); + +export default Innleggelsesperiodeliste; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeliste/innleggelsesperiodeliste.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeliste/innleggelsesperiodeliste.css" new file mode 100644 index 0000000000..9ec89c8150 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeliste/innleggelsesperiodeliste.css" @@ -0,0 +1,23 @@ +.innleggelsesperiodeliste { + list-style-type: none; + margin: 0; + padding: 0; +} + +.innleggelsesperiodeliste__element { + border-bottom: 1px solid #c8c8c8; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.innleggelsesperiodeliste__element:not(:first-child) { + margin-top: 0.5rem; +} + +.lenkeContainer { + float: right; +} + +.lenkeContainer__slettLenke { + margin-left: 1rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeoversikt/Innleggelsesperiodeoversikt.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeoversikt/Innleggelsesperiodeoversikt.tsx" new file mode 100644 index 0000000000..09f3351fa1 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeoversikt/Innleggelsesperiodeoversikt.tsx" @@ -0,0 +1,176 @@ +import { Loader } from '@navikt/ds-react'; +import { Box, LinkButton, Margin, PageError, TitleWithUnderline } from '@navikt/ft-plattform-komponenter'; +import { httpUtils, Period } from '@fpsak-frontend/utils'; +import React, { useEffect, useMemo } from 'react'; +import { postInnleggelsesperioder, postInnleggelsesperioderDryRun } from '../../../api/api'; +import LinkRel from '../../../constants/LinkRel'; +import { InnleggelsesperiodeResponse } from '../../../types/InnleggelsesperiodeResponse'; +import { findLinkByRel } from '../../../util/linkUtils'; +import ContainerContext from '../../context/ContainerContext'; +import AddButton from '../add-button/AddButton'; +import InnleggelsesperiodeFormModal, { FieldName } from '../innleggelsesperiodeFormModal/InnleggelsesperiodeFormModal'; +import Innleggelsesperiodeliste from '../innleggelsesperiodeliste/Innleggelsesperiodeliste'; +import WriteAccessBoundContent from '../write-access-bound-content/WriteAccessBoundContent'; +import styles from './innleggelsesperiodeoversikt.css'; + +interface InnleggelsesperiodeoversiktProps { + onInnleggelsesperioderUpdated: () => void; +} + +const Innleggelsesperiodeoversikt = ({ + onInnleggelsesperioderUpdated, +}: InnleggelsesperiodeoversiktProps): JSX.Element => { + const { endpoints, httpErrorHandler } = React.useContext(ContainerContext); + + const [modalIsOpen, setModalIsOpen] = React.useState(false); + const [innleggelsesperioderResponse, setInnleggelsesperioderResponse] = React.useState({ + perioder: [], + links: [], + versjon: null, + behandlingUuid: '', + }); + const [isLoading, setIsLoading] = React.useState(true); + const [hentInnleggelsesperioderFeilet, setHentInnleggelsesperioderFeilet] = React.useState(false); + const [lagreInnleggelsesperioderFeilet, setLagreInnleggelsesperioderFeilet] = React.useState(false); + const controller = useMemo(() => new AbortController(), []); + + const innleggelsesperioder = innleggelsesperioderResponse.perioder; + const innleggelsesperioderDefault = innleggelsesperioder?.length > 0 ? innleggelsesperioder : [new Period('', '')]; + + const hentInnleggelsesperioder = () => + httpUtils.get(`${endpoints.innleggelsesperioder}`, httpErrorHandler, { + signal: controller.signal, + }); + + const initializeInnleggelsesperiodeData = (response: InnleggelsesperiodeResponse) => ({ + ...response, + perioder: response.perioder.map(({ fom, tom }) => new Period(fom, tom)), + }); + + const updateInnlegelsesperioder = () => { + setIsLoading(true); + hentInnleggelsesperioder() + .then((response: InnleggelsesperiodeResponse) => { + setInnleggelsesperioderResponse(initializeInnleggelsesperiodeData(response)); + setIsLoading(false); + }) + .catch(() => { + setHentInnleggelsesperioderFeilet(true); + }); + }; + + const lagreInnleggelsesperioder = formState => { + setIsLoading(true); + let nyeInnleggelsesperioder = []; + if (formState.innleggelsesperioder?.length > 0) { + nyeInnleggelsesperioder = formState.innleggelsesperioder + .filter(periodeWrapper => periodeWrapper.period?.fom && periodeWrapper.period?.tom) + .map(periodeWrapper => new Period(periodeWrapper.period.fom, periodeWrapper.period.tom)); + } + + const { href } = findLinkByRel(LinkRel.ENDRE_INNLEGGELSESPERIODER, innleggelsesperioderResponse.links); + const { behandlingUuid, versjon } = innleggelsesperioderResponse; + postInnleggelsesperioder( + href, + { behandlingUuid, versjon, perioder: nyeInnleggelsesperioder }, + httpErrorHandler, + controller.signal, + ) + .then(() => { + onInnleggelsesperioderUpdated(); + updateInnlegelsesperioder(); + }) + .catch(() => { + setLagreInnleggelsesperioderFeilet(true); + setIsLoading(false); + }); + }; + + useEffect(() => { + let isMounted = true; + hentInnleggelsesperioder() + .then((response: InnleggelsesperiodeResponse) => { + if (isMounted) { + setInnleggelsesperioderResponse(initializeInnleggelsesperiodeData(response)); + setIsLoading(false); + } + }) + .catch(() => { + setHentInnleggelsesperioderFeilet(true); + }); + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + if (hentInnleggelsesperioderFeilet || lagreInnleggelsesperioderFeilet) { + return ; + } + + return ( +
    + ( + <> + 0} + contentRenderer={() => ( + setModalIsOpen(true)} + > + Rediger liste + + )} + /> + ( + setModalIsOpen(true)} id="leggTilPeriodeKnapp" /> + )} + /> + + )} + > + Innleggelsesperioder + + {isLoading ? ( + + ) : ( + + {innleggelsesperioder.length === 0 &&

    Ingen innleggelsesperioder registrert

    } + {innleggelsesperioder.length > 0 && ( + + + + )} +
    + )} + {modalIsOpen && ( + { + const { href, requestPayload } = findLinkByRel( + LinkRel.ENDRE_INNLEGGELSESPERIODER, + innleggelsesperioderResponse.links, + ); + return postInnleggelsesperioderDryRun( + href, + { ...requestPayload, perioder: nyeInnleggelsesperioder }, + httpErrorHandler, + controller.signal, + ); + }} + /> + )} +
    + ); +}; + +export default Innleggelsesperiodeoversikt; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeoversikt/innleggelsesperiodeoversikt.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeoversikt/innleggelsesperiodeoversikt.css" new file mode 100644 index 0000000000..36a55ca6c5 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/innleggelsesperiodeoversikt/innleggelsesperiodeoversikt.css" @@ -0,0 +1,11 @@ +.innleggelsesperiodeoversikt { + width: 100%; +} + +.innleggelsesperiodeoversikt__titleContainer { + display: flex; +} + +.innleggelsesperiodeoversikt__redigerListeKnapp { + margin-left: 1.5rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/lagre-vurdering-feilet-melding/LagreVurderingFeiletMelding.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/lagre-vurdering-feilet-melding/LagreVurderingFeiletMelding.tsx" new file mode 100644 index 0000000000..7751422edc --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/lagre-vurdering-feilet-melding/LagreVurderingFeiletMelding.tsx" @@ -0,0 +1,10 @@ +import React from 'react'; +import { Alert } from '@navikt/ds-react'; + +const LagreVurderingFeiletMelding = (): JSX.Element => ( + + Noe gikk galt ved lagring av vurderingen, vennligst prøv igjen senere. + +); + +export default LagreVurderingFeiletMelding; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/mangler-gyldig-signatur-melding/ManglerGyldigSignaturMelding.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/mangler-gyldig-signatur-melding/ManglerGyldigSignaturMelding.tsx" new file mode 100644 index 0000000000..776de9565d --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/mangler-gyldig-signatur-melding/ManglerGyldigSignaturMelding.tsx" @@ -0,0 +1,14 @@ +import React from 'react'; +import { Alert } from '@navikt/ds-react'; + +interface ManglerGyldigSignaturMeldingProps { + children: React.ReactNode; +} + +const ManglerGyldigSignaturMelding = ({ children }: ManglerGyldigSignaturMeldingProps): JSX.Element => ( + + {children} + +); + +export default ManglerGyldigSignaturMelding; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/MedisinskVilk\303\245r.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/MedisinskVilk\303\245r.tsx" new file mode 100644 index 0000000000..9c19e9c139 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/MedisinskVilk\303\245r.tsx" @@ -0,0 +1,369 @@ +import { Tabs } from '@navikt/ds-react'; +import { Box, ChildIcon, Infostripe, Margin, PageContainer, WarningIcon } from '@navikt/ft-plattform-komponenter'; +import { httpUtils } from '@fpsak-frontend/utils'; +import classnames from 'classnames'; +import React, { useMemo } from 'react'; +import { useQuery } from 'react-query'; +import FagsakYtelseType from '../../../constants/FagsakYtelseType'; +import { DiagnosekodeResponse } from '../../../types/DiagnosekodeResponse'; +import Dokument from '../../../types/Dokument'; +import { NyeDokumenterResponse } from '../../../types/NyeDokumenterResponse'; +import Step, { + langvarigSykdomSteg, + livetsSluttfaseSteg, + StepId, + tilsynOgPleieSteg, + toOmsorgspersonerSteg, +} from '../../../types/Step'; +import SykdomsstegStatusResponse from '../../../types/SykdomsstegStatusResponse'; +import Vurderingstype from '../../../types/Vurderingstype'; +import { + finnNesteStegForLivetsSluttfase, + finnNesteStegForOpplæringspenger, + finnNesteStegForPleiepenger, + stegForSakstype, +} from '../../../util/statusUtils'; +import { erFagsakOLPEllerPLS } from '../../../util/utils'; +import ContainerContext from '../../context/ContainerContext'; +import VurderingContext from '../../context/VurderingContext'; +import AksjonspunktFerdigStripe from '../aksjonspunkt-ferdig-stripe/AksjonspunktFerdigStripe'; +// eslint-disable-next-line max-len +import NyeDokumenterSomKanPåvirkeEksisterendeVurderingerController from '../nye-dokumenter-som-kan-påvirke-eksisterende-vurderinger/NyeDokumenterSomKanPåvirkeEksisterendeVurderingerController'; +import StruktureringAvDokumentasjon from '../strukturering-av-dokumentasjon/StruktureringAvDokumentasjon'; +import UteståendeEndringerMelding from '../utestående-endringer-melding/UteståendeEndringerMelding'; +import VilkarsvurderingAvLivetsSluttfase from '../vilkarsvurdering-av-livets-sluttfase/VilkarsvurderingAvLivetsSluttfase'; +import VilkårsvurderingAvTilsynOgPleie from '../vilkårsvurdering-av-tilsyn-og-pleie/VilkårsvurderingAvTilsynOgPleie'; +import VilkårsvurderingAvToOmsorgspersoner from '../vilkårsvurdering-av-to-omsorgspersoner/VilkårsvurderingAvToOmsorgspersoner'; +import VilkårsvurderingLangvarigSykdom from '../vilkårsvurdering-langvarig-sykdom/VilkarsvurderingLangvarigSykdom'; +import WriteAccessBoundContent from '../write-access-bound-content/WriteAccessBoundContent'; +import ActionType from './actionTypes'; +import styles from './medisinskVilkår.css'; +import medisinskVilkårReducer from './reducer'; + +interface TabItemProps { + label: string; + showWarningIcon: boolean; +} + +const TabItem = ({ label, showWarningIcon }: TabItemProps) => { + const cls = classnames(styles.medisinskVilkårTabItem, { + [styles.medisinskVilkårTabItemExtended]: showWarningIcon, + }); + return ( +
    + {label} + {showWarningIcon && ( +
    + +
    + )} +
    + ); +}; + +const sykdomTittel = (fagsakYtelseType: FagsakYtelseType) => { + if (fagsakYtelseType === FagsakYtelseType.OPPLÆRINGSPENGER) { + return 'Sykdom og opplæring'; + } + + if (fagsakYtelseType === FagsakYtelseType.PLEIEPENGER_SLUTTFASE) { + return 'Livets sluttfase'; + } + + return 'Sykdom'; +}; + +const MedisinskVilkår = (): JSX.Element => { + const [state, dispatch] = React.useReducer(medisinskVilkårReducer, { + isLoading: true, + hasError: null, + activeStep: null, + markedStep: null, + sykdomsstegStatus: null, + nyeDokumenterSomIkkeErVurdert: [], + }); + + const { isLoading, hasError, activeStep, markedStep, sykdomsstegStatus, nyeDokumenterSomIkkeErVurdert } = state; + const { endpoints, httpErrorHandler, visFortsettknapp, fagsakYtelseType } = React.useContext(ContainerContext); + + const dokumentStegForSakstype = stegForSakstype(fagsakYtelseType).find(stepObj => stepObj.id === StepId.Dokument); + + const finnNesteStegFn = (nesteSteg: SykdomsstegStatusResponse, isOnMount?: boolean) => { + switch (fagsakYtelseType) { + case FagsakYtelseType.OPPLÆRINGSPENGER: + return finnNesteStegForOpplæringspenger(nesteSteg, isOnMount); + case FagsakYtelseType.PLEIEPENGER_SLUTTFASE: + return finnNesteStegForLivetsSluttfase(nesteSteg, isOnMount); + default: + return finnNesteStegForPleiepenger(nesteSteg, isOnMount); + } + }; + + const controller = useMemo(() => new AbortController(), []); + + const hentDiagnosekoder = () => + httpUtils + .get(endpoints.diagnosekoder, httpErrorHandler) + .then((response: DiagnosekodeResponse) => response); + + const { isLoading: diagnosekoderLoading, data: diagnosekoderData } = useQuery( + 'diagnosekodeResponse', + hentDiagnosekoder, + { enabled: !erFagsakOLPEllerPLS(fagsakYtelseType) }, + ); + + const diagnosekoder = endpoints.diagnosekoder ? diagnosekoderData?.diagnosekoder : []; + const diagnosekoderTekst = diagnosekoder?.length > 0 ? `${diagnosekoder?.join(', ')}` : 'Kode mangler'; + + const hentSykdomsstegStatus = async () => { + try { + const status = await httpUtils.get(endpoints.status, httpErrorHandler, { + signal: controller.signal, + }); + const nesteSteg = finnNesteStegFn(status); + dispatch({ + type: ActionType.UPDATE_STATUS, + sykdomsstegStatus: status, + step: nesteSteg, + }); + return status; + } catch (error) { + dispatch({ + type: ActionType.SHOW_ERROR, + }); + throw new Error(error); + } + }; + + const hentNyeDokumenterSomIkkeErVurdertHvisNødvendig = (status): Promise<[SykdomsstegStatusResponse, Dokument[]]> => + new Promise((resolve, reject) => { + if (status.nyttDokumentHarIkkekontrollertEksisterendeVurderinger) { + httpUtils + .get(endpoints.nyeDokumenter, httpErrorHandler, { + signal: controller.signal, + }) + .then( + dokumenter => resolve([status, dokumenter]), + error => reject(error), + ); + } else { + resolve([status, []]); + } + }); + + React.useEffect(() => { + hentSykdomsstegStatus() + .then(hentNyeDokumenterSomIkkeErVurdertHvisNødvendig) + .then(([sykdomsstegStatusResponse, nyeDokumenterSomIkkeErVurdertResponse]) => { + const step = finnNesteStegFn(sykdomsstegStatusResponse, true); + if (step !== null) { + dispatch({ + type: ActionType.MARK_AND_ACTIVATE_STEP, + step, + nyeDokumenterSomIkkeErVurdert: nyeDokumenterSomIkkeErVurdertResponse, + }); + } else { + dispatch({ + type: ActionType.ACTIVATE_DEFAULT_STEP, + step: dokumentStegForSakstype, + nyeDokumenterSomIkkeErVurdert: nyeDokumenterSomIkkeErVurdertResponse, + }); + } + }); + + return () => { + controller.abort(); + }; + }, []); + + const navigerTilNesteSteg = () => { + const nesteSteg = finnNesteStegFn(sykdomsstegStatus); + dispatch({ type: ActionType.NAVIGATE_TO_STEP, step: nesteSteg }); + }; + + const navigerTilSteg = (nesteSteg: Step, ikkeMarkerSteg?: boolean) => { + if (sykdomsstegStatus.kanLøseAksjonspunkt || ikkeMarkerSteg) { + dispatch({ type: ActionType.ACTIVATE_STEP_AND_CLEAR_MARKING, step: nesteSteg }); + } else { + dispatch({ type: ActionType.NAVIGATE_TO_STEP, step: nesteSteg }); + } + }; + + const afterEndringerUtifraNyeDokumenterRegistrert = () => { + dispatch({ type: ActionType.ENDRINGER_UTIFRA_NYE_DOKUMENTER_REGISTRERT }); + hentSykdomsstegStatus().then( + ({ + kanLøseAksjonspunkt, + manglerVurderingAvKontinuerligTilsynOgPleie, + manglerVurderingAvToOmsorgspersoner, + manglerVurderingAvLangvarigSykdom, + }) => { + if (kanLøseAksjonspunkt) { + navigerTilSteg(toOmsorgspersonerSteg, true); + } else if (!manglerVurderingAvKontinuerligTilsynOgPleie && manglerVurderingAvToOmsorgspersoner) { + navigerTilSteg(toOmsorgspersonerSteg); + } else if (manglerVurderingAvLangvarigSykdom) { + navigerTilSteg(langvarigSykdomSteg); + } + }, + ); + }; + + const kanLøseAksjonspunkt = sykdomsstegStatus?.kanLøseAksjonspunkt; + const harDataSomIkkeHarBlittTattMedIBehandling = sykdomsstegStatus?.harDataSomIkkeHarBlittTattMedIBehandling; + const manglerVurderingAvNyeDokumenter = sykdomsstegStatus?.nyttDokumentHarIkkekontrollertEksisterendeVurderinger; + + const steps: Step[] = stegForSakstype(fagsakYtelseType); + + const contextValue = useMemo(() => { + if (activeStep === tilsynOgPleieSteg) { + return { vurderingstype: Vurderingstype.KONTINUERLIG_TILSYN_OG_PLEIE }; + } + if (activeStep === langvarigSykdomSteg) { + return { vurderingstype: Vurderingstype.LANGVARIG_SYKDOM }; + } + if (activeStep === livetsSluttfaseSteg) { + return { vurderingstype: Vurderingstype.LIVETS_SLUTTFASE }; + } + if (activeStep === toOmsorgspersonerSteg) { + return { vurderingstype: Vurderingstype.TO_OMSORGSPERSONER }; + } + return {}; + }, [activeStep]); + return ( + + + Sykdomsvurderingen gjelder barnet og er felles for alle parter. + Diagnose: + + {(diagnosekoderLoading && ' ') || diagnosekoderTekst} + + + ) : ( + Vurderingen gjelder pleietrengende og er felles for alle parter. + ) + } + iconRenderer={() => } + /> + +
    +

    {sykdomTittel(fagsakYtelseType)}

    + ( + + + + )} + otherRequirementsAreMet={ + !!( + nyeDokumenterSomIkkeErVurdert && + manglerVurderingAvNyeDokumenter && + markedStep !== dokumentStegForSakstype && + activeStep !== dokumentStegForSakstype + ) + } + /> + + } + otherRequirementsAreMet={ + !!( + kanLøseAksjonspunkt && + visFortsettknapp === true && + markedStep !== dokumentStegForSakstype && + activeStep !== dokumentStegForSakstype + ) + } + /> + } + otherRequirementsAreMet={ + kanLøseAksjonspunkt && !!harDataSomIkkeHarBlittTattMedIBehandling && visFortsettknapp === false + } + /> + + { + dispatch({ type: ActionType.ACTIVATE_STEP, step: steps.find(step => step.id === clickedId) }); + }} + > + + {steps.map(step => ( + } + /> + ))} + + +
    +
    + {activeStep === dokumentStegForSakstype && ( + + hentSykdomsstegStatus() + .then(hentNyeDokumenterSomIkkeErVurdertHvisNødvendig) + .then(([status, dokumenter]) => { + dispatch({ + type: ActionType.UPDATE_NYE_DOKUMENTER_SOM_IKKE_ER_VURDERT, + nyeDokumenterSomIkkeErVurdert: dokumenter, + }); + return [status, dokumenter]; + }) + } + sykdomsstegStatus={sykdomsstegStatus} + /> + )} + {activeStep === tilsynOgPleieSteg && ( + + + + )} + {activeStep === langvarigSykdomSteg && ( + + + + )} + {activeStep === livetsSluttfaseSteg && ( + + + + )} + {activeStep === toOmsorgspersonerSteg && ( + + + + )} +
    +
    +
    +
    + ); +}; + +export default MedisinskVilkår; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/actionTypes.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/actionTypes.ts" new file mode 100644 index 0000000000..90cf7dff0e --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/actionTypes.ts" @@ -0,0 +1,13 @@ +enum ActionType { + MARK_AND_ACTIVATE_STEP = 'markAndActivateStep', + ACTIVATE_STEP = 'activateStep', + ACTIVATE_DEFAULT_STEP = 'activateDefaultStep', + ACTIVATE_STEP_AND_CLEAR_MARKING = 'activateStepAndClearMarking', + NAVIGATE_TO_STEP = 'navigateToStep', + UPDATE_STATUS = 'updateStatus', + SHOW_ERROR = 'showError', + ENDRINGER_UTIFRA_NYE_DOKUMENTER_REGISTRERT = 'endringerUtifraNyeDokumenterRegistrert', + UPDATE_NYE_DOKUMENTER_SOM_IKKE_ER_VURDERT = 'updateNyeDokumenterSomIkkeErVurdert', +} + +export default ActionType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/medisinskVilk\303\245r.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/medisinskVilk\303\245r.css" new file mode 100644 index 0000000000..96d3e41927 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/medisinskVilk\303\245r.css" @@ -0,0 +1,56 @@ +.medisinskVilkår { + color: #262626; +} + +.medisinskVilkår__vilkårContentContainer { + margin-top: 1.5rem; +} + +@media screen and (min-width: 1350px) { + .medisinskVilkår__vilkårContentContainer { + min-height: 1250px; + } +} + +.medisinskVilkår :global .navds-alert { + padding-top: 0.4375rem; + padding-bottom: 0.4375rem; +} + +.medisinskVilkår :global .navds-alert__wrapper { + display: flex; + max-width: 47rem; +} + +.medisinskVilkår :global .navigationWithDetailView__navigationSection { + flex-shrink: 0; + min-width: 25.3125rem; +} + +.medisinskVilkår :global .navigationWithDetailView__detailSection { + padding-bottom: 9.375rem; +} + +.medisinskVilkårTabItem { + position: relative; +} + +.medisinskVilkårTabItem__warningIcon { + position: absolute; + right: 0; + bottom: -2px; + height: 1.5rem; +} + +.medisinskVilkårTabItemExtended { + padding-right: 2rem; +} + +.infostripe__diagnosekode__tittel { + margin-left: 1.75rem; +} + +.infostripe__diagnosekode { + margin-left: 0.5rem; + font-weight: bold; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/reducer.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/reducer.ts" new file mode 100644 index 0000000000..5515ff7181 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/reducer.ts" @@ -0,0 +1,91 @@ +import ActionType from './actionTypes'; +import Step, { dokumentSteg } from '../../../types/Step'; +import SykdomsstegStatusResponse from '../../../types/SykdomsstegStatusResponse'; +import Dokument from '../../../types/Dokument'; + +interface State { + isLoading: boolean; + hasError: boolean; + activeStep: Step; + markedStep: Step; + sykdomsstegStatus: SykdomsstegStatusResponse; + nyeDokumenterSomIkkeErVurdert: Dokument[]; +} + +interface Action { + type: ActionType; + step?: Step; + sykdomsstegStatus?: SykdomsstegStatusResponse; + nyeDokumenterSomIkkeErVurdert?: Dokument[]; +} + +const medisinskVilkårReducer = (state: State, action: Action): State => { + switch (action.type) { + case ActionType.MARK_AND_ACTIVATE_STEP: { + return { + ...state, + nyeDokumenterSomIkkeErVurdert: action.nyeDokumenterSomIkkeErVurdert || [], + activeStep: action.step || dokumentSteg, + markedStep: action.step, + isLoading: false, + }; + } + case ActionType.ACTIVATE_STEP: { + return { + ...state, + activeStep: action.step, + }; + } + case ActionType.ACTIVATE_STEP_AND_CLEAR_MARKING: { + return { + ...state, + activeStep: action.step, + markedStep: null, + }; + } + case ActionType.ACTIVATE_DEFAULT_STEP: { + return { + ...state, + nyeDokumenterSomIkkeErVurdert: action.nyeDokumenterSomIkkeErVurdert || [], + activeStep: dokumentSteg, + isLoading: false, + }; + } + case ActionType.NAVIGATE_TO_STEP: { + return { + ...state, + activeStep: action.step, + markedStep: action.step, + }; + } + case ActionType.UPDATE_STATUS: { + return { + ...state, + sykdomsstegStatus: action.sykdomsstegStatus, + markedStep: action.step, + }; + } + case ActionType.SHOW_ERROR: { + return { + ...state, + hasError: true, + }; + } + case ActionType.ENDRINGER_UTIFRA_NYE_DOKUMENTER_REGISTRERT: { + return { + ...state, + nyeDokumenterSomIkkeErVurdert: [], + }; + } + case ActionType.UPDATE_NYE_DOKUMENTER_SOM_IKKE_ER_VURDERT: { + return { + ...state, + nyeDokumenterSomIkkeErVurdert: action.nyeDokumenterSomIkkeErVurdert, + }; + } + default: + return state; + } +}; + +export default medisinskVilkårReducer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/tests/MedisinskVilk\303\245r.spec.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/tests/MedisinskVilk\303\245r.spec.tsx" new file mode 100644 index 0000000000..29216a2a01 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/medisinsk-vilk\303\245r/tests/MedisinskVilk\303\245r.spec.tsx" @@ -0,0 +1,178 @@ +import { httpUtils } from '@fpsak-frontend/utils'; +import { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { QueryClientProvider } from 'react-query'; +import ContainerContract from '../../../../types/ContainerContract'; +import ContainerContext from '../../../context/ContainerContext'; +import queryClient from '../../../context/queryClient'; +import MedisinskVilkår from '../MedisinskVilkår'; + +const statusEndpointMock = 'statusEndpointMock'; + +const vurderingsoversiktMock = { + perioderSomKanVurderes: [], + resterendeVurderingsperioder: [{ fom: '2028-01-01', tom: '2028-01-01' }], + resterendeValgfrieVurderingsperioder: [], + søknadsperioderTilBehandling: [], + vurderingselementer: [], + links: [], +} as any; + +const httpErrorHandlerMock = () => null; +const contextWrapper = (ui, contextValues?: Partial) => + render( + + + {ui} + + , + ); + +const renderMedisinskVilkår = (contextValues?: Partial) => + contextWrapper(, contextValues); + +describe('MedisinskVilkår', () => { + let httpGetSpy = null; + + beforeAll(() => { + httpGetSpy = jest.spyOn(httpUtils, 'get'); + }); + + const mockResolvedGetApiCall = data => { + httpGetSpy.mockImplementation( + () => + new Promise(resolve => { + resolve(data); + }), + ); + }; + + it('should render spinner while getting sykdomsstegStatus, then render Infostripe with text when data has been received', async () => { + mockResolvedGetApiCall({ manglerDiagnosekode: true }); + const { getByText } = renderMedisinskVilkår(); + expect(getByText('venter...')).toBeInTheDocument(); + await waitFor(() => { + expect(getByText(/Sykdomsvurderingen gjelder barnet og er felles for alle parter./)).toBeInTheDocument(); + }); + }); + + it.skip('should activate dokument-step by default when that is the step that needs work next', async () => { + mockResolvedGetApiCall({ manglerGodkjentLegeerklæring: true, dokumenter: [] }); + const { getByText } = renderMedisinskVilkår(); + expect(getByText('venter...')).toBeInTheDocument(); + await waitFor(() => { + expect(getByText(/Ingen dokumenter å vise/i)).toBeInTheDocument(); + }); + }); + + it.skip('should activate ktp-step by default when that is the step that needs work next', async () => { + mockResolvedGetApiCall({ manglerVurderingAvKontinuerligTilsynOgPleie: true, ...vurderingsoversiktMock }); + const { getByText } = renderMedisinskVilkår(); + expect(getByText('venter...')).toBeInTheDocument(); + await waitFor(() => { + expect(getByText(/Vurdering av tilsyn og pleie/i)).toBeInTheDocument(); + }); + }); + + it.skip('should activate to omsorgspersoner-step by default when that is the step that needs work next', async () => { + mockResolvedGetApiCall({ manglerVurderingAvToOmsorgspersoner: true, ...vurderingsoversiktMock }); + const { getByText } = renderMedisinskVilkår(); + expect(getByText('venter...')).toBeInTheDocument(); + await waitFor(() => { + expect(getByText(/Vurdering av to omsorgspersoner/i)).toBeInTheDocument(); + }); + }); + + it('should render Fortsett-button if aksjonspunkt is solveable and user is in KTP or 2OP step, but hidden in dokument step', async () => { + mockResolvedGetApiCall({ + kanLøseAksjonspunkt: true, + ...vurderingsoversiktMock, + }); + const { getByText, getAllByText, queryByText } = renderMedisinskVilkår({ + readOnly: false, + visFortsettknapp: true, + }); + expect(getByText('venter...')).toBeInTheDocument(); + expect(queryByText(/Sykdom er ferdig vurdert og du kan gå videre i behandlingen/i)).toBeNull(); + expect(queryByText(/OBS! Det er gjort endringer i sykdomssteget/i)).toBeNull(); + await waitFor(async () => { + userEvent.click(getAllByText(/Tilsyn og pleie/i)[0]); + expect(getByText(/Sykdom er ferdig vurdert og du kan gå videre i behandlingen/i)).toBeInTheDocument(); + userEvent.click(getAllByText(/To omsorgspersoner/i)[0]); + expect(getByText(/Sykdom er ferdig vurdert og du kan gå videre i behandlingen/i)).toBeInTheDocument(); + }); + }); + + it('should render Fortsett-button with warning message if harDataSomIkkeHarBlittTattMedIBehandling while aksjonspunkt is not active', async () => { + mockResolvedGetApiCall({ + kanLøseAksjonspunkt: true, + harDataSomIkkeHarBlittTattMedIBehandling: true, + ...vurderingsoversiktMock, + }); + const { getByText, getAllByText } = renderMedisinskVilkår({ + readOnly: false, + visFortsettknapp: false, + }); + expect(getByText('venter...')).toBeInTheDocument(); + await waitFor(async () => { + expect(getByText(/OBS! Det er gjort endringer i sykdomssteget/i)).toBeInTheDocument(); + userEvent.click(getAllByText(/Tilsyn og pleie/i)[0]); + expect(getByText(/OBS! Det er gjort endringer i sykdomssteget/i)).toBeInTheDocument(); + userEvent.click(getAllByText(/To omsorgspersoner/i)[0]); + expect(getByText(/OBS! Det er gjort endringer i sykdomssteget/i)).toBeInTheDocument(); + }); + }); + + it('should call the provided onFinished-function when clicking standard Fortsett-button to solve aksjonspunkt', async () => { + const onFinishedWrapper = { onFinished: () => null }; + const onFinishedSpy = jest.spyOn(onFinishedWrapper, 'onFinished'); + + mockResolvedGetApiCall({ + kanLøseAksjonspunkt: true, + ...vurderingsoversiktMock, + }); + const { getByText, getAllByText } = renderMedisinskVilkår({ + readOnly: false, + visFortsettknapp: true, + onFinished: onFinishedWrapper.onFinished, + }); + expect(getByText('venter...')).toBeInTheDocument(); + await waitFor(async () => { + userEvent.click(getAllByText(/Tilsyn og pleie/i)[0]); + }); + expect(onFinishedSpy).toHaveBeenCalledTimes(0); + await waitFor(async () => { + userEvent.click(getAllByText(/Fortsett/i)[0]); + expect(onFinishedSpy).toHaveBeenCalledTimes(1); + }); + }); + + it('should call the provided onFinished-function when clicking Fortsett-button with warning message to solve aksjonspunkt', async () => { + const onFinishedWrapper = { onFinished: () => null }; + const onFinishedSpy = jest.spyOn(onFinishedWrapper, 'onFinished'); + + mockResolvedGetApiCall({ + kanLøseAksjonspunkt: true, + harDataSomIkkeHarBlittTattMedIBehandling: true, + ...vurderingsoversiktMock, + }); + const { getByText, getAllByText } = renderMedisinskVilkår({ + readOnly: false, + visFortsettknapp: false, + }); + expect(getByText('venter...')).toBeInTheDocument(); + await waitFor(async () => { + expect(onFinishedSpy).not.toHaveBeenCalled(); + userEvent.click(getAllByText(/Fortsett/i)[0]); + }); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ny-vurdering-controller/NyVurderingController.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ny-vurdering-controller/NyVurderingController.tsx" new file mode 100644 index 0000000000..9e3663619c --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ny-vurdering-controller/NyVurderingController.tsx" @@ -0,0 +1,183 @@ +import { httpUtils, Period } from '@fpsak-frontend/utils'; +import { PageContainer, Box, Margin } from '@navikt/ft-plattform-komponenter'; +import React, { useMemo } from 'react'; +import Dokument from '../../../types/Dokument'; +import Link from '../../../types/Link'; +import { Vurderingsversjon } from '../../../types/Vurdering'; +import { PeriodeMedEndring, PerioderMedEndringResponse } from '../../../types/PeriodeMedEndring'; +import OverlappendePeriodeModal from '../overlappende-periode-modal/OverlappendePeriodeModal'; +import ActionType from './actionTypes'; +import vurderingControllerReducer from './reducer'; +import { postNyVurdering, postNyVurderingDryRun } from '../../../api/api'; +import ContainerContext from '../../context/ContainerContext'; +import scrollUp from '../../../util/viewUtils'; +import VurderingContext from '../../context/VurderingContext'; +import LagreVurderingFeiletMelding from '../lagre-vurdering-feilet-melding/LagreVurderingFeiletMelding'; + +interface NyVurderingControllerProps { + opprettVurderingLink: Link; + dataTilVurderingUrl: string; + onVurderingLagret: () => void; + formRenderer: ( + dokumenter: Dokument[], + onSubmit: (vurderingsversjon: Vurderingsversjon) => void, + isSubmitting: boolean, + ) => React.ReactNode; +} + +const NyVurderingController = ({ + opprettVurderingLink, + dataTilVurderingUrl, + onVurderingLagret, + formRenderer, +}: NyVurderingControllerProps): JSX.Element => { + const { httpErrorHandler } = React.useContext(ContainerContext); + const { vurderingstype } = React.useContext(VurderingContext); + + const [state, dispatch] = React.useReducer(vurderingControllerReducer, { + sjekkForEksisterendeVurderingerPågår: false, + lagringAvVurderingPågår: false, + lagreVurderingHarFeilet: false, + hentDataTilVurderingPågår: true, + hentDataTilVurderingHarFeilet: false, + dokumenter: [], + perioderMedEndring: [], + overlappendePeriodeModalOpen: false, + vurderingsversjonTilLagringFraModal: null, + }); + + const { + sjekkForEksisterendeVurderingerPågår, + lagringAvVurderingPågår, + lagreVurderingHarFeilet, + hentDataTilVurderingPågår, + hentDataTilVurderingHarFeilet, + dokumenter, + perioderMedEndring, + overlappendePeriodeModalOpen, + vurderingsversjonTilLagringFraModal, + } = state; + const controller = useMemo(() => new AbortController(), []); + + function lagreVurdering(nyVurderingsversjon: Partial) { + dispatch({ type: ActionType.LAGRING_AV_VURDERING_PÅBEGYNT }); + return postNyVurdering( + opprettVurderingLink.href, + opprettVurderingLink.requestPayload.behandlingUuid, + { ...nyVurderingsversjon, type: vurderingstype }, + httpErrorHandler, + controller.signal, + ).then( + () => { + onVurderingLagret(); + dispatch({ type: ActionType.VURDERING_LAGRET }); + scrollUp(); + }, + () => { + dispatch({ type: ActionType.LAGRE_VURDERING_FEILET }); + scrollUp(); + }, + ); + } + + const sjekkForEksisterendeVurderinger = ( + nyVurderingsversjon: Vurderingsversjon, + ): Promise => + postNyVurderingDryRun( + opprettVurderingLink.href, + opprettVurderingLink.requestPayload.behandlingUuid, + { ...nyVurderingsversjon, type: vurderingstype }, + httpErrorHandler, + controller.signal, + ); + + const advarOmEksisterendeVurderinger = ( + nyVurderingsversjon: Vurderingsversjon, + perioderMedEndringValue: PeriodeMedEndring[], + ) => { + dispatch({ + type: ActionType.ADVAR_OM_EKSISTERENDE_VURDERINGER, + perioderMedEndring: perioderMedEndringValue, + vurderingsversjonTilLagringFraModal: nyVurderingsversjon, + }); + }; + + const initializePerioderMedEndringer = (perioderMedEndringResponse: PerioderMedEndringResponse) => + perioderMedEndringResponse.perioderMedEndringer.map(({ periode: { fom, tom }, ...otherFields }) => ({ + periode: new Period(fom, tom), + ...otherFields, + })); + + const beOmBekreftelseFørLagringHvisNødvendig = (nyVurderingsversjon: Vurderingsversjon) => { + dispatch({ type: ActionType.SJEKK_FOR_EKSISTERENDE_VURDERINGER_PÅBEGYNT }); + sjekkForEksisterendeVurderinger(nyVurderingsversjon).then( + perioderMedEndringerResponse => { + const perioderMedEndringer = initializePerioderMedEndringer(perioderMedEndringerResponse); + const harOverlappendePerioder = perioderMedEndringer?.length > 0; + if (harOverlappendePerioder) { + advarOmEksisterendeVurderinger(nyVurderingsversjon, perioderMedEndringer); + } else { + lagreVurdering(nyVurderingsversjon); + } + }, + () => { + dispatch({ type: ActionType.LAGRE_VURDERING_FEILET }); + }, + ); + }; + + function hentDataTilVurdering(): Promise { + if (!dataTilVurderingUrl) { + return new Promise(resolve => { + resolve([]); + }); + } + return httpUtils.get(dataTilVurderingUrl, httpErrorHandler, { signal: controller.signal }); + } + + const handleHentDataTilVurderingError = () => { + dispatch({ type: ActionType.HENT_DATA_TIL_VURDERING_HAR_FEILET }); + }; + + React.useEffect(() => { + let isMounted = true; + dispatch({ type: ActionType.HENT_DATA_TIL_VURDERING }); + hentDataTilVurdering() + .then((dokumenterResponse: Dokument[]) => { + if (isMounted) { + dispatch({ type: ActionType.HENTET_DATA_TIL_VURDERING, dokumenter: dokumenterResponse }); + } + }) + .catch(handleHentDataTilVurderingError); + + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + const isSubmitting = lagringAvVurderingPågår || sjekkForEksisterendeVurderingerPågår; + return ( + + {lagreVurderingHarFeilet && ( + + + + )} + {formRenderer(dokumenter, beOmBekreftelseFørLagringHvisNødvendig, isSubmitting)} + dispatch({ type: ActionType.LAGRING_AV_VURDERING_AVBRUTT })} + onConfirm={() => { + lagreVurdering(vurderingsversjonTilLagringFraModal).then(() => { + dispatch({ type: ActionType.VURDERING_LAGRET, perioderMedEndring }); + }); + }} + isOpen={overlappendePeriodeModalOpen} + isSubmitting={lagringAvVurderingPågår} + /> + + ); +}; + +export default NyVurderingController; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ny-vurdering-controller/actionTypes.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ny-vurdering-controller/actionTypes.ts" new file mode 100644 index 0000000000..c2bf5e9f10 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ny-vurdering-controller/actionTypes.ts" @@ -0,0 +1,13 @@ +enum ActionType { + SJEKK_FOR_EKSISTERENDE_VURDERINGER_PÅBEGYNT = 'sjekkForEksisterendeVurderingerPåbegynt', + LAGRING_AV_VURDERING_PÅBEGYNT = 'lagringAvVurderingPåbegynt', + VURDERING_LAGRET = 'vurderingLagret', + LAGRE_VURDERING_FEILET = 'lagreVurderingFeilet', + LAGRING_AV_VURDERING_AVBRUTT = 'lagringAvVurderingAvbrutt', + ADVAR_OM_EKSISTERENDE_VURDERINGER = 'advarOmEksisterendeVurderinger', + HENT_DATA_TIL_VURDERING = 'hentDataTilVurdering', + HENTET_DATA_TIL_VURDERING = 'hentetDataTilVurdering', + HENT_DATA_TIL_VURDERING_HAR_FEILET = 'hentDataTilVurderingHarFeilet', +} + +export default ActionType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ny-vurdering-controller/reducer.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ny-vurdering-controller/reducer.ts" new file mode 100644 index 0000000000..6e716e2ded --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ny-vurdering-controller/reducer.ts" @@ -0,0 +1,92 @@ +import ActionType from './actionTypes'; +import Dokument from '../../../types/Dokument'; +import { PeriodeMedEndring } from '../../../types/PeriodeMedEndring'; +import { Vurderingsversjon } from '../../../types/Vurdering'; +import { dokumentSorter } from '../../../util/dokumentUtils'; + +interface State { + sjekkForEksisterendeVurderingerPågår: boolean; + lagringAvVurderingPågår: boolean; + lagreVurderingHarFeilet: boolean; + hentDataTilVurderingPågår: boolean; + hentDataTilVurderingHarFeilet: boolean; + dokumenter: Dokument[]; + perioderMedEndring: PeriodeMedEndring[]; + overlappendePeriodeModalOpen: boolean; + vurderingsversjonTilLagringFraModal: Vurderingsversjon | null; +} + +interface Action { + type: ActionType; + perioderMedEndring?: PeriodeMedEndring[]; + dokumenter?: Dokument[]; + vurderingsversjonTilLagringFraModal?: Vurderingsversjon; +} + +const vurderingControllerReducer = (state: State, action: Action): State => { + switch (action.type) { + case ActionType.SJEKK_FOR_EKSISTERENDE_VURDERINGER_PÅBEGYNT: + return { + ...state, + sjekkForEksisterendeVurderingerPågår: true, + }; + case ActionType.LAGRING_AV_VURDERING_PÅBEGYNT: + return { + ...state, + lagringAvVurderingPågår: true, + lagreVurderingHarFeilet: false, + }; + case ActionType.VURDERING_LAGRET: + return { + ...state, + lagringAvVurderingPågår: false, + perioderMedEndring: null, + overlappendePeriodeModalOpen: false, + }; + case ActionType.LAGRE_VURDERING_FEILET: + return { + ...state, + lagringAvVurderingPågår: false, + lagreVurderingHarFeilet: true, + sjekkForEksisterendeVurderingerPågår: false, + }; + case ActionType.LAGRING_AV_VURDERING_AVBRUTT: + return { + ...state, + overlappendePeriodeModalOpen: false, + vurderingsversjonTilLagringFraModal: null, + }; + case ActionType.ADVAR_OM_EKSISTERENDE_VURDERINGER: + return { + ...state, + overlappendePeriodeModalOpen: true, + sjekkForEksisterendeVurderingerPågår: false, + perioderMedEndring: action.perioderMedEndring, + vurderingsversjonTilLagringFraModal: action.vurderingsversjonTilLagringFraModal, + }; + case ActionType.HENT_DATA_TIL_VURDERING: + return { + ...state, + hentDataTilVurderingPågår: true, + hentDataTilVurderingHarFeilet: false, + }; + case ActionType.HENTET_DATA_TIL_VURDERING: { + const dokumenter = action.dokumenter?.sort(dokumentSorter); + return { + ...state, + dokumenter, + hentDataTilVurderingPågår: false, + }; + } + case ActionType.HENT_DATA_TIL_VURDERING_HAR_FEILET: + return { + ...state, + hentDataTilVurderingPågår: false, + hentDataTilVurderingHarFeilet: true, + }; + default: + return state; + } +}; + +export default vurderingControllerReducer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/nye-dokumenter-som-kan-p\303\245virke-eksisterende-vurderinger/NyeDokumenterSomKanP\303\245virkeEksisterendeVurderinger.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/nye-dokumenter-som-kan-p\303\245virke-eksisterende-vurderinger/NyeDokumenterSomKanP\303\245virkeEksisterendeVurderinger.tsx" new file mode 100644 index 0000000000..4da25e88e2 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/nye-dokumenter-som-kan-p\303\245virke-eksisterende-vurderinger/NyeDokumenterSomKanP\303\245virkeEksisterendeVurderinger.tsx" @@ -0,0 +1,81 @@ +import { Button, Label } from '@navikt/ds-react'; +import { Box, DocumentIcon, Margin } from '@navikt/ft-plattform-komponenter'; +import { prettifyDateString } from '@fpsak-frontend/utils'; +import React from 'react'; +import LinkRel from '../../../constants/LinkRel'; +import Dokument, { Dokumenttype } from '../../../types/Dokument'; +import { findLinkByRel } from '../../../util/linkUtils'; +import styles from './nyeDokumenterSomKanPåvirkeEksisterendeVurderinger.css'; + +interface NyeDokumenterListeProps { + dokumenter: Dokument[]; +} + +const getDokumentLabel = (type: Dokumenttype) => { + if (type === Dokumenttype.LEGEERKLÆRING) { + return 'Legeerklæring'; + } + if (type === Dokumenttype.ANDRE_MEDISINSKE_OPPLYSNINGER) { + return 'Andre medisinske opplysninger'; + } + if (type === Dokumenttype.EPIKRISE) { + return 'Epikrise'; + } + return null; +}; + +const NyeDokumenterListe = ({ dokumenter }: NyeDokumenterListeProps) => ( + <> + {dokumenter.map(dokument => { + const dokumentLink = findLinkByRel(LinkRel.DOKUMENT_INNHOLD, dokument.links); + return ( +

    + Nytt dokument: + + + + + {`${getDokumentLabel(dokument.type)} (datert ${prettifyDateString(dokument.datert)})`} + +

    + ); + })} + +); + +interface NyeDokumenterSomKanPåvirkeEksisterendeVurderingerProps { + dokumenter: Dokument[]; + onEndringerRegistrertClick: () => void; + isSubmitting: boolean; +} + +const NyeDokumenterSomKanPåvirkeEksisterendeVurderinger = ({ + dokumenter, + onEndringerRegistrertClick, + isSubmitting, +}: NyeDokumenterSomKanPåvirkeEksisterendeVurderingerProps): JSX.Element => ( +
    + +
    + + + + + +
    +
    +
    +); + +export default NyeDokumenterSomKanPåvirkeEksisterendeVurderinger; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/nye-dokumenter-som-kan-p\303\245virke-eksisterende-vurderinger/NyeDokumenterSomKanP\303\245virkeEksisterendeVurderingerController.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/nye-dokumenter-som-kan-p\303\245virke-eksisterende-vurderinger/NyeDokumenterSomKanP\303\245virkeEksisterendeVurderingerController.tsx" new file mode 100644 index 0000000000..cc699acb4a --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/nye-dokumenter-som-kan-p\303\245virke-eksisterende-vurderinger/NyeDokumenterSomKanP\303\245virkeEksisterendeVurderingerController.tsx" @@ -0,0 +1,58 @@ +import React, { useMemo } from 'react'; +import { Box, Margin, PageContainer } from '@navikt/ft-plattform-komponenter'; +import { httpUtils } from '@fpsak-frontend/utils'; +import Dokument from '../../../types/Dokument'; +import ContainerContext from '../../context/ContainerContext'; +import NyeDokumenterSomKanPåvirkeEksisterendeVurderinger from './NyeDokumenterSomKanPåvirkeEksisterendeVurderinger'; + +interface NyeDokumenterSomKanPåvirkeEksisterendeVurderingerControllerProps { + dokumenter: Dokument[]; + afterEndringerRegistrert: () => void; +} + +const NyeDokumenterSomKanPåvirkeEksisterendeVurderingerController = ({ + dokumenter, + afterEndringerRegistrert, +}: NyeDokumenterSomKanPåvirkeEksisterendeVurderingerControllerProps): JSX.Element => { + const { endpoints, httpErrorHandler, behandlingUuid } = React.useContext(ContainerContext); + const controller = useMemo(() => new AbortController(), []); + const [isSubmitting, setIsSubmitting] = React.useState(false); + const [httpErrorHasOccured, setHttpErrorHasOccured] = React.useState(false); + + const createRegistrerNyeDokumenterRequestPayload = () => ({ + behandlingUuid, + dokumenterSomSkalUtkvitteres: dokumenter.map(({ id }) => id), + }); + + const bekreftAtEndringerErRegistrert = () => + httpUtils.post(endpoints.nyeDokumenter, createRegistrerNyeDokumenterRequestPayload(), httpErrorHandler, { + signal: controller.signal, + }); + + const onEndringerRegistrertClick = async () => { + setIsSubmitting(true); + setHttpErrorHasOccured(false); + try { + await bekreftAtEndringerErRegistrert(); + afterEndringerRegistrert(); + setIsSubmitting(false); + } catch (error) { + setHttpErrorHasOccured(true); + setIsSubmitting(false); + } + }; + + return ( + + + + + + ); +}; + +export default NyeDokumenterSomKanPåvirkeEksisterendeVurderingerController; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/nye-dokumenter-som-kan-p\303\245virke-eksisterende-vurderinger/nyeDokumenterSomKanP\303\245virkeEksisterendeVurderinger.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/nye-dokumenter-som-kan-p\303\245virke-eksisterende-vurderinger/nyeDokumenterSomKanP\303\245virkeEksisterendeVurderinger.css" new file mode 100644 index 0000000000..01088c4cd1 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/nye-dokumenter-som-kan-p\303\245virke-eksisterende-vurderinger/nyeDokumenterSomKanP\303\245virkeEksisterendeVurderinger.css" @@ -0,0 +1,23 @@ +.nyeDokumenterSomKanPåvirkeEksisterendeVurderinger { + max-width: 75.25rem; +} + +.nyeDokumenterSomKanPåvirkeEksisterendeVurderinger__dokumentLink { + margin-left: 0.5rem; +} + +.nyeDokumenterSomKanPåvirkeEksisterendeVurderinger__ikonContainer { + position: relative; + top: 6px; + margin-right: 0.5rem; +} + +.nyeDokumenterSomKanPåvirkeEksisterendeVurderinger__ikonContainer svg { + height: 1.375rem; + width: 1.125rem; +} + +.nyeDokumenterSomKanPåvirkeEksisterendeVurderinger__content { + border: 2px solid #ff9100; + padding: 1.25rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/overlappende-periode-modal/OverlappendePeriodeModal.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/overlappende-periode-modal/OverlappendePeriodeModal.tsx" new file mode 100644 index 0000000000..5a22d823ef --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/overlappende-periode-modal/OverlappendePeriodeModal.tsx" @@ -0,0 +1,67 @@ +import { Alert, BodyShort, Heading } from '@navikt/ds-react'; +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import { PeriodeMedEndring } from '../../../types/PeriodeMedEndring'; +import ConfirmationModal from '../confirmation-modal/ConfirmationModal'; + +interface OverlappendePeriodeModalProps { + perioderMedEndring: PeriodeMedEndring[]; + onConfirm: () => void; + onCancel: () => void; + isOpen: boolean; + isSubmitting: boolean; +} + +const renderInfoMsg = ({ periode }: PeriodeMedEndring) => ( + + + {`${periode.prettifyPeriod()} overlapper med en tidligere vurdert periode lagt til i denne behandlingen. Den nye + vurderingen vil erstatte den gamle.`} + + +); + +const renderWarningMsg = ({ periode }: PeriodeMedEndring) => ( + + + {`${periode.prettifyPeriod()} overlapper med en tidligere vurdert periode. Dersom ny vurdering medfører endring i + resultat vil det bli sendt melding om nytt vedtak til bruker. Dette vil også gjelde eventuelle andre parter.`} + + +); + +const OverlappendePeriodeModal = ({ + perioderMedEndring, + onConfirm, + onCancel, + isOpen, + isSubmitting, +}: OverlappendePeriodeModalProps): JSX.Element => { + const overlappendePerioderISammeBehandling = + perioderMedEndring.filter(({ endrerVurderingSammeBehandling }) => endrerVurderingSammeBehandling === true) || []; + const overlappendePerioderIAndreBehandlinger = + perioderMedEndring.filter(({ endrerAnnenVurdering }) => endrerAnnenVurdering === true) || []; + + const harFlerePerioderMedOverlapp = perioderMedEndring.length > 1; + + return ( + + + Overlappende periode + + + {overlappendePerioderISammeBehandling.map(renderInfoMsg)} + {overlappendePerioderIAndreBehandlinger.map(renderWarningMsg)} + + + {`Er du sikker på at du vil erstatte ${ + harFlerePerioderMedOverlapp ? 'de tidligere vurderte periodene' : 'den tidligere vurderte perioden' + }?`} + + + + + ); +}; + +export default OverlappendePeriodeModal; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/overlappende-s\303\270knadsperiode-panel/OverlappendeS\303\270knadsperiodePanel.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/overlappende-s\303\270knadsperiode-panel/OverlappendeS\303\270knadsperiodePanel.tsx" new file mode 100644 index 0000000000..61d8670fba --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/overlappende-s\303\270knadsperiode-panel/OverlappendeS\303\270knadsperiodePanel.tsx" @@ -0,0 +1,29 @@ +import { BodyShort, Button } from '@navikt/ds-react'; +import { InfoPanel } from '@navikt/ft-plattform-komponenter'; +import { Period, prettifyPeriodList } from '@fpsak-frontend/utils'; +import React from 'react'; + +interface OverlappendeSøknadsperiodePanelProps { + onProgressButtonClick: () => void; + overlappendeVurderingsperioder: Period[]; +} + +const OverlappendeSøknadsperiodePanel = ({ + onProgressButtonClick, + overlappendeVurderingsperioder, +}: OverlappendeSøknadsperiodePanelProps): JSX.Element => ( + + + {`Søknadsperioden overlapper med en eller flere tidligere vurderte perioder + (${prettifyPeriodList( + overlappendeVurderingsperioder, + )}). Vurder om det er grunnlag for å gjøre en ny vurdering for + denne perioden.`} + + + +); + +export default OverlappendeSøknadsperiodePanel; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/period-list/PeriodList.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/period-list/PeriodList.tsx" new file mode 100644 index 0000000000..501c460cfc --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/period-list/PeriodList.tsx" @@ -0,0 +1,25 @@ +import { Period } from '@fpsak-frontend/utils'; +import { CalendarIcon } from '@navikt/ft-plattform-komponenter'; +import * as React from 'react'; +import classnames from 'classnames'; +import styles from './periodList.css'; + +interface PeriodListProps { + periods: Period[]; + className?: string; +} + +const PeriodList = ({ periods, className }: PeriodListProps): JSX.Element => { + const cls = classnames(styles.periodList, { + [className]: !!className, + }); + return ( +
    +
    + +
    + {periods.map(period => period.prettifyPeriod()).join(', ')} +
    + ); +}; +export default PeriodList; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/period-list/periodList.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/period-list/periodList.css" new file mode 100644 index 0000000000..ab3d2c52a7 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/period-list/periodList.css" @@ -0,0 +1,13 @@ +.periodList { + display: flex; +} + +.periodList__calendarIcon { + flex-shrink: 0; +} + +.periodList__periods { + position: relative; + left: 2px; + line-height: 1.5; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/signert-seksjon/SignertSeksjon.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/signert-seksjon/SignertSeksjon.tsx" new file mode 100644 index 0000000000..6392302589 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/signert-seksjon/SignertSeksjon.tsx" @@ -0,0 +1,29 @@ +import { Box, Margin, TitleWithUnderline, GreenCheckIcon, WarningIcon } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import IconWithText from '../icon-with-text/IconWithText'; + +interface SignertSeksjonProps { + harGyldigSignatur: boolean; +} + +const SignertSeksjon = ({ harGyldigSignatur }: SignertSeksjonProps): JSX.Element => ( +
    + Godkjent signatur + + {harGyldigSignatur && ( + } + text="Det finnes dokumentasjon som er signert av sykehuslege eller lege fra spesialisthelsetjenesten." + /> + )} + {!harGyldigSignatur && ( + } + text="Ingen legeerklæring fra sykehuslege/spesialisthelsetjenesten registrert." + /> + )} + +
    +); + +export default SignertSeksjon; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/slett-duplikat-modal/SlettDuplikatModal.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/slett-duplikat-modal/SlettDuplikatModal.tsx" new file mode 100644 index 0000000000..5a71852e98 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/slett-duplikat-modal/SlettDuplikatModal.tsx" @@ -0,0 +1,88 @@ +import { BodyShort, Button, Modal } from '@navikt/ds-react'; +import { Box, Margin, PageError } from '@navikt/ft-plattform-komponenter'; +import { post } from '@fpsak-frontend/utils'; +import React, { useMemo, useState } from 'react'; +import LinkRel from '../../../constants/LinkRel'; +import Dokument from '../../../types/Dokument'; +import { findLinkByRel } from '../../../util/linkUtils'; +import scrollUp from '../../../util/viewUtils'; +import ContainerContext from '../../context/ContainerContext'; +import styles from './slettDuplikatModal.css'; + +interface SlettDuplikatModalProps { + handleCloseModal: () => void; + selectedDocument: Dokument; + onRemove: () => void; +} + +const SlettDuplikatModal = ({ handleCloseModal, selectedDocument, onRemove }: SlettDuplikatModalProps): JSX.Element => { + const { httpErrorHandler } = React.useContext(ContainerContext); + const controller = useMemo(() => new AbortController(), []); + const [isSubmitting, setIsSubmitting] = useState(false); + const [removeDuplikatFeilet, setRemoveDuplikatFeilet] = useState(false); + + React.useEffect( + () => () => { + controller.abort(); + }, + [], + ); + const removeDuplikatreferanse = () => { + const endreDkumentLink = findLinkByRel(LinkRel.ENDRE_DOKUMENT, selectedDocument.links); + const { href, requestPayload } = endreDkumentLink; + + const dokumentUtenDuplikat = { + ...selectedDocument, + duplikatAvId: null, + }; + setRemoveDuplikatFeilet(false); + setIsSubmitting(true); + post(href, { ...requestPayload, ...dokumentUtenDuplikat }, httpErrorHandler, { + signal: controller.signal, + }).then( + () => { + setIsSubmitting(false); + scrollUp(); + onRemove(); + }, + () => { + setIsSubmitting(false); + scrollUp(); + setRemoveDuplikatFeilet(true); + }, + ); + }; + return ( + + + + Når du fjerner et dokument som duplikat vil det bli lagt som et eget dokument i listen. + + {removeDuplikatFeilet && ( + + + + )} +
    + +
    + +
    +
    +
    +
    + ); +}; +export default SlettDuplikatModal; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/slett-duplikat-modal/slettDuplikatModal.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/slett-duplikat-modal/slettDuplikatModal.css" new file mode 100644 index 0000000000..5eaeb93b66 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/slett-duplikat-modal/slettDuplikatModal.css" @@ -0,0 +1,9 @@ +.buttonContainer { + display: flex; + margin-top: 2.25rem; +} + +.cancelButton { + margin-left: 1rem; + margin-top: -0.125rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-controller/StrukturerDokumentController.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-controller/StrukturerDokumentController.tsx" new file mode 100644 index 0000000000..f621119185 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-controller/StrukturerDokumentController.tsx" @@ -0,0 +1,134 @@ +import { httpUtils } from '@fpsak-frontend/utils'; +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import { Alert } from '@navikt/ds-react'; +import React, { useMemo } from 'react'; +import FagsakYtelseType from '../../../constants/FagsakYtelseType'; +import Dokument from '../../../types/Dokument'; +import Link from '../../../types/Link'; +import scrollUp from '../../../util/viewUtils'; +import ContainerContext from '../../context/ContainerContext'; +import StrukturerDokumentForm from '../strukturer-dokument-form/StrukturerDokumentForm'; +import StrukturerDokumentSluttfaseForm from '../strukturer-dokument-sluttfase-form/StrukturerDokumentSluttfaseForm'; +import StrukturerDokumentOpplaeringspengerForm from '../strukturer-dokument-opplaeringspenger-form/StrukturerDokumentOpplaeringspengerForm'; +import { erFagsakOLPEllerPLS } from '../../../util/utils'; + +interface StrukturerDokumentControllerProps { + strukturerDokumentLink: Link; + dokument: Dokument; + onDokumentStrukturert: () => void; + editMode?: boolean; + strukturerteDokumenter: Dokument[]; +} + +const StrukturerDokumentController = ({ + dokument, + strukturerDokumentLink: { requestPayload, href }, + onDokumentStrukturert, + editMode, + strukturerteDokumenter, +}: StrukturerDokumentControllerProps): JSX.Element => { + const { httpErrorHandler, fagsakYtelseType } = React.useContext(ContainerContext); + const [isSubmitting, setIsSubmitting] = React.useState(false); + const controller = useMemo(() => new AbortController(), []); + const [submitDocumentError, setSubmitDocumentError] = React.useState(null); + + React.useEffect( + () => () => { + controller.abort(); + }, + [], + ); + + const strukturerDokument = (strukturertDokument: Dokument) => { + setIsSubmitting(true); + setSubmitDocumentError(null); + httpUtils + .post(href, { ...requestPayload, ...strukturertDokument }, httpErrorHandler, { + signal: controller.signal, + }) + .then( + () => { + setIsSubmitting(false); + onDokumentStrukturert(); + scrollUp(); + }, + error => { + setSubmitDocumentError(error); + setIsSubmitting(false); + scrollUp(); + }, + ); + }; + + const hasError = !!submitDocumentError; + const getErrorMessage = () => { + if (submitDocumentError?.response?.data?.feilkode) { + const { feilkode } = submitDocumentError.response.data; + if (feilkode === 'K9-6701') { + return 'Kan ikke sette at et dokument er duplikat av et annet duplikat dokument.'; + } + if (feilkode === 'K9-6702') { + return 'Kan ikke sette duplikatdokumenter på tvers av pleietrengende.'; + } + if (feilkode === 'K9-6703') { + return 'Kan ikke sette som duplikat siden dokumentet har blitt brukt i en vurdering.'; + } + if (feilkode === 'K9-6704') { + return 'Kan ikke sette som duplikat siden andre dokumenter er duplikat av dette dokumentet.'; + } + } + return 'Noe gikk galt, vennligst prøv igjen senere'; + }; + + return ( + <> + {hasError && ( + + + {getErrorMessage()} + + + )} + {!erFagsakOLPEllerPLS(fagsakYtelseType) && ( + + )} + {fagsakYtelseType === FagsakYtelseType.PLEIEPENGER_SLUTTFASE && ( + + )} + {fagsakYtelseType === FagsakYtelseType.OPPLÆRINGSPENGER && ( + + )} + + {hasError && ( + + + {getErrorMessage()} + + + )} + + ); +}; + +export default StrukturerDokumentController; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-form/StrukturerDokumentForm.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-form/StrukturerDokumentForm.tsx" new file mode 100644 index 0000000000..4df2ac0ff5 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-form/StrukturerDokumentForm.tsx" @@ -0,0 +1,102 @@ +import { dateConstants } from '@fpsak-frontend/utils'; +import { DatepickerRHF, RadioGroupPanelRHF } from '@fpsak-frontend/form'; +import { Box, DetailView, Form, Margin } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import LinkRel from '../../../constants/LinkRel'; +import { Dokumenttype } from '../../../types/Dokument'; +import { + StrukturerDokumentFormFieldName as FieldName, + StrukturerDokumentFormState, +} from '../../../types/StrukturerDokumentFormState'; +import { lagStrukturertDokument } from '../../../util/dokumentUtils'; +import { findLinkByRel } from '../../../util/linkUtils'; +import ContainerContext from '../../context/ContainerContext'; +import { dateIsNotInTheFuture, required } from '../../form/validators'; +import DokumentKnapp from '../dokument-knapp/DokumentKnapp'; +import DuplikatRadiobuttons from '../duplikat-radiobuttons/DuplikatRadiobuttons'; +import StrukturerDokumentFormProps from '../../../types/StrukturerDokumentFormProps'; + +const StrukturerDokumentForm = ({ + dokument, + onSubmit, + editMode, + isSubmitting, + strukturerteDokumenter, +}: StrukturerDokumentFormProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + + const formMethods = useForm({ + defaultValues: editMode && { + [FieldName.INNEHOLDER_MEDISINSKE_OPPLYSNINGER]: dokument.type, + [FieldName.DATERT]: dokument.datert, + }, + }); + + const dokumentLink = findLinkByRel(LinkRel.DOKUMENT_INNHOLD, dokument.links); + + const lagNyttStrukturertDokument = (formState: StrukturerDokumentFormState) => { + onSubmit(lagStrukturertDokument(formState, dokument)); + }; + + const buttonLabel = editMode === true ? 'Oppdater' : 'Bekreft'; + + return ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + +
    + + + + + + + + + + + +
    +
    + ); +}; + +export default StrukturerDokumentForm; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-opplaeringspenger-form/StrukturerDokumentOpplaeringspengerForm.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-opplaeringspenger-form/StrukturerDokumentOpplaeringspengerForm.tsx" new file mode 100644 index 0000000000..9d1b1c6023 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-opplaeringspenger-form/StrukturerDokumentOpplaeringspengerForm.tsx" @@ -0,0 +1,101 @@ +import { dateConstants } from '@fpsak-frontend/utils'; +import { DatepickerRHF, RadioGroupPanelRHF } from '@fpsak-frontend/form'; +import { Box, DetailView, Form, Margin } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import LinkRel from '../../../constants/LinkRel'; +import { Dokumenttype } from '../../../types/Dokument'; +import { + StrukturerDokumentFormFieldName as FieldName, + StrukturerDokumentFormState, +} from '../../../types/StrukturerDokumentFormState'; +import { lagStrukturertDokument } from '../../../util/dokumentUtils'; +import { findLinkByRel } from '../../../util/linkUtils'; +import ContainerContext from '../../context/ContainerContext'; +import { dateIsNotInTheFuture, required } from '../../form/validators'; +import DokumentKnapp from '../dokument-knapp/DokumentKnapp'; +import DuplikatRadiobuttons from '../duplikat-radiobuttons/DuplikatRadiobuttons'; +import StrukturerDokumentFormProps from '../../../types/StrukturerDokumentFormProps'; + +const StrukturerDokumentOpplaeringspengerForm = ({ + dokument, + onSubmit, + editMode, + isSubmitting, + strukturerteDokumenter, +}: StrukturerDokumentFormProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + + const formMethods = useForm({ + defaultValues: editMode && { + [FieldName.INNEHOLDER_MEDISINSKE_OPPLYSNINGER]: dokument.type, + [FieldName.DATERT]: dokument.datert, + }, + }); + + const dokumentLink = findLinkByRel(LinkRel.DOKUMENT_INNHOLD, dokument.links); + + const lagNyttStrukturertDokument = (formState: StrukturerDokumentFormState) => { + onSubmit(lagStrukturertDokument(formState, dokument)); + }; + + const buttonLabel = editMode === true ? 'Oppdater' : 'Bekreft'; + + return ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + +
    + + + + + + + + + + + +
    +
    + ); +}; + +export default StrukturerDokumentOpplaeringspengerForm; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-sluttfase-form/StrukturerDokumentSluttfaseForm.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-sluttfase-form/StrukturerDokumentSluttfaseForm.tsx" new file mode 100644 index 0000000000..13994b9618 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturer-dokument-sluttfase-form/StrukturerDokumentSluttfaseForm.tsx" @@ -0,0 +1,93 @@ +import { dateConstants } from '@fpsak-frontend/utils'; +import { DatepickerRHF, RadioGroupPanelRHF } from '@fpsak-frontend/form'; +import { Box, DetailView, Form, Margin } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import LinkRel from '../../../constants/LinkRel'; +import { Dokumenttype } from '../../../types/Dokument'; +import { + StrukturerDokumentFormFieldName as FieldName, + StrukturerDokumentFormState, +} from '../../../types/StrukturerDokumentFormState'; +import { lagStrukturertDokument } from '../../../util/dokumentUtils'; +import { findLinkByRel } from '../../../util/linkUtils'; +import ContainerContext from '../../context/ContainerContext'; +import { dateIsNotInTheFuture, required } from '../../form/validators'; +import DokumentKnapp from '../dokument-knapp/DokumentKnapp'; +import DuplikatRadiobuttons from '../duplikat-radiobuttons/DuplikatRadiobuttons'; +import StrukturerDokumentFormProps from '../../../types/StrukturerDokumentFormProps'; + +const StrukturerDokumentSluttfaseForm = ({ + dokument, + onSubmit, + editMode, + isSubmitting, + strukturerteDokumenter, +}: StrukturerDokumentFormProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + + const formMethods = useForm({ + defaultValues: editMode && { + [FieldName.INNEHOLDER_MEDISINSKE_OPPLYSNINGER]: dokument.type, + [FieldName.DATERT]: dokument.datert, + }, + }); + + const dokumentLink = findLinkByRel(LinkRel.DOKUMENT_INNHOLD, dokument.links); + + const lagNyttStrukturertDokument = (formState: StrukturerDokumentFormState) => { + onSubmit(lagStrukturertDokument(formState, dokument)); + }; + + const buttonLabel = editMode === true ? 'Oppdater' : 'Bekreft'; + + return ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + +
    + + + + + + + + + + + +
    +
    + ); +}; + +export default StrukturerDokumentSluttfaseForm; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/StruktureringAvDokumentasjon.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/StruktureringAvDokumentasjon.tsx" new file mode 100644 index 0000000000..2134275d80 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/StruktureringAvDokumentasjon.tsx" @@ -0,0 +1,199 @@ +import { get } from '@fpsak-frontend/utils'; +import { Box, Margin, NavigationWithDetailView, PageContainer } from '@navikt/ft-plattform-komponenter'; +import axios from 'axios'; +import React, { useMemo } from 'react'; +import FagsakYtelseType from '../../../constants/FagsakYtelseType'; +import Dokument from '../../../types/Dokument'; +import Dokumentoversikt from '../../../types/Dokumentoversikt'; +import { DokumentoversiktResponse } from '../../../types/DokumentoversiktResponse'; +import { StepId } from '../../../types/Step'; +import SykdomsstegStatusResponse from '../../../types/SykdomsstegStatusResponse'; +import { + nesteStegErLivetssluttfase, + nesteStegErOpplæringspenger, + nesteStegErVurderingForPleiepenger, +} from '../../../util/statusUtils'; +import ContainerContext from '../../context/ContainerContext'; +import Diagnosekodeoversikt from '../diagnosekodeoversikt/Diagnosekodeoversikt'; +import DokumentasjonFooter from '../dokumentasjon-footer/DokumentasjonFooter'; +import Dokumentdetaljer from '../dokumentdetaljer/Dokumentdetaljer'; +import Dokumentnavigasjon from '../dokumentnavigasjon/Dokumentnavigasjon'; +import DokumentoversiktMessages from '../dokumentoversikt-messages/DokmentoversiktMessages'; +import Innleggelsesperiodeoversikt from '../innleggelsesperiodeoversikt/Innleggelsesperiodeoversikt'; +import SignertSeksjon from '../signert-seksjon/SignertSeksjon'; +import ActionType from './actionTypes'; +import dokumentReducer from './reducer'; +import styles from './struktureringAvDokumentasjon.css'; + +interface StruktureringAvDokumentasjonProps { + navigerTilNesteSteg: () => void; + hentSykdomsstegStatus: () => Promise<[SykdomsstegStatusResponse, Dokument[]]>; + sykdomsstegStatus: SykdomsstegStatusResponse; +} + +const StruktureringAvDokumentasjon = ({ + navigerTilNesteSteg, + hentSykdomsstegStatus, + sykdomsstegStatus, +}: StruktureringAvDokumentasjonProps): JSX.Element => { + const { endpoints, httpErrorHandler, fagsakYtelseType } = React.useContext(ContainerContext); + const httpCanceler = useMemo(() => axios.CancelToken.source(), []); + + const [state, dispatch] = React.useReducer(dokumentReducer, { + visDokumentDetails: false, + isLoading: true, + dokumentoversikt: null, + valgtDokument: null, + dokumentoversiktFeilet: false, + visRedigeringAvDokument: false, + }); + + const { + dokumentoversikt, + isLoading, + visDokumentDetails, + valgtDokument, + dokumentoversiktFeilet, + visRedigeringAvDokument, + } = state; + + const skalViseInnleggelsesperioderOgDiagnosekoder = ![ + FagsakYtelseType.PLEIEPENGER_SLUTTFASE, + FagsakYtelseType.OPPLÆRINGSPENGER, + ].includes(fagsakYtelseType); + + const nesteStegErVurderingFn = (nesteSteg: SykdomsstegStatusResponse) => { + if (fagsakYtelseType === FagsakYtelseType.PLEIEPENGER_SLUTTFASE) { + return nesteStegErLivetssluttfase(nesteSteg); + } + + if (fagsakYtelseType === FagsakYtelseType.OPPLÆRINGSPENGER) { + return nesteStegErOpplæringspenger(nesteSteg); + } + + return nesteStegErVurderingForPleiepenger(nesteSteg); + }; + + const getDokumentoversikt = () => + get(endpoints.dokumentoversikt, httpErrorHandler, { + cancelToken: httpCanceler.token, + }); + + const visDokumentoversikt = (nyDokumentoversikt: Dokumentoversikt) => { + dispatch({ + type: ActionType.VIS_DOKUMENTOVERSIKT, + dokumentoversikt: nyDokumentoversikt, + valgtDokument: nyDokumentoversikt.harUstrukturerteDokumenter() + ? nyDokumentoversikt.ustrukturerteDokumenter[0] + : null, + }); + }; + + const handleError = () => { + dispatch({ type: ActionType.DOKUMENTOVERSIKT_FEILET }); + }; + + const velgDokument = (nyttValgtDokument: Dokument) => { + dispatch({ type: ActionType.VELG_DOKUMENT, valgtDokument: nyttValgtDokument }); + }; + + const åpneDokumentSomMåBehandles = ({ ustrukturerteDokumenter }: Dokumentoversikt) => { + const sisteDokumentIndex = ustrukturerteDokumenter?.length > 0 ? ustrukturerteDokumenter.length - 1 : null; + const førsteDokumentSomMåBehandles = + sisteDokumentIndex !== null ? ustrukturerteDokumenter[sisteDokumentIndex] : null; + if (førsteDokumentSomMåBehandles) { + velgDokument(førsteDokumentSomMåBehandles); + } + }; + + React.useEffect(() => { + let isMounted = true; + getDokumentoversikt() + .then(({ dokumenter }: DokumentoversiktResponse) => { + if (isMounted) { + const nyDokumentoversikt = new Dokumentoversikt(dokumenter); + visDokumentoversikt(nyDokumentoversikt); + åpneDokumentSomMåBehandles(nyDokumentoversikt); + } + }) + .catch(handleError); + return () => { + isMounted = false; + httpCanceler.cancel(); + }; + }, []); + + const sjekkStatus = () => { + dispatch({ type: ActionType.PENDING }); + hentSykdomsstegStatus() + .then(() => { + getDokumentoversikt().then(({ dokumenter }: DokumentoversiktResponse) => { + const nyDokumentoversikt = new Dokumentoversikt(dokumenter); + visDokumentoversikt(nyDokumentoversikt); + åpneDokumentSomMåBehandles(nyDokumentoversikt); + }); + }) + .catch(handleError); + }; + + return ( + + + {dokumentoversikt?.harDokumenter() === true && ( +
    + ( + <> + + + + + + )} + showDetailSection={visDokumentDetails} + detailSection={() => ( + dispatch({ type: ActionType.REDIGER_DOKUMENT })} + strukturerteDokumenter={dokumentoversikt?.strukturerteDokumenter} + /> + )} + /> + + {skalViseInnleggelsesperioderOgDiagnosekoder && ( + + } + secondSectionRenderer={() => } + thirdSectionRenderer={() => } + /> + + )} +
    + )} +
    + ); +}; + +export default StruktureringAvDokumentasjon; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/actionTypes.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/actionTypes.ts" new file mode 100644 index 0000000000..e60f1484d1 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/actionTypes.ts" @@ -0,0 +1,9 @@ +enum ActionType { + VIS_DOKUMENTOVERSIKT = 'visDokumentoversikt', + VELG_DOKUMENT = 'velgDokument', + PENDING = 'pending', + DOKUMENTOVERSIKT_FEILET = 'dokumentoversiktFeilet', + REDIGER_DOKUMENT = 'redigerDokument', +} + +export default ActionType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/reducer.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/reducer.ts" new file mode 100644 index 0000000000..e01eae577f --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/reducer.ts" @@ -0,0 +1,62 @@ +import Dokument from '../../../types/Dokument'; +import ActionType from './actionTypes'; +import Dokumentoversikt from '../../../types/Dokumentoversikt'; + +interface State { + visDokumentDetails: boolean; + isLoading: boolean; + dokumentoversikt: Dokumentoversikt; + valgtDokument: Dokument; + dokumentoversiktFeilet: boolean; + visRedigeringAvDokument: boolean; +} + +interface Action { + type: ActionType; + dokumentoversikt?: Dokumentoversikt; + valgtDokument?: Dokument; +} + +const vilkårsdokumentReducer = (state: State, action: Action): State => { + switch (action.type) { + case ActionType.VIS_DOKUMENTOVERSIKT: { + return { + ...state, + dokumentoversikt: action.dokumentoversikt, + valgtDokument: action.valgtDokument, + isLoading: false, + visDokumentDetails: false, + dokumentoversiktFeilet: false, + }; + } + case ActionType.DOKUMENTOVERSIKT_FEILET: { + return { + ...state, + isLoading: false, + dokumentoversiktFeilet: true, + }; + } + case ActionType.VELG_DOKUMENT: + return { + ...state, + valgtDokument: action.valgtDokument, + visRedigeringAvDokument: false, + visDokumentDetails: true, + }; + case ActionType.PENDING: + return { + ...state, + isLoading: true, + dokumentoversiktFeilet: false, + }; + case ActionType.REDIGER_DOKUMENT: + return { + ...state, + visRedigeringAvDokument: true, + }; + default: + return state; + } +}; + +export default vilkårsdokumentReducer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/struktureringAvDokumentasjon.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/struktureringAvDokumentasjon.css" new file mode 100644 index 0000000000..f4a624acf6 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/struktureringAvDokumentasjon.css" @@ -0,0 +1,3 @@ +.dokumentoversikt :global .navigationWithDetailView__navigationSection { + border: 0; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/tests/StruktureringAvDokumentasjon.spec.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/tests/StruktureringAvDokumentasjon.spec.tsx" new file mode 100644 index 0000000000..8728fc6e71 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturering-av-dokumentasjon/tests/StruktureringAvDokumentasjon.spec.tsx" @@ -0,0 +1,130 @@ +import { render, waitFor } from '@testing-library/react'; +import React from 'react'; +import { httpUtils } from '@fpsak-frontend/utils'; +import ContainerContext from '../../../context/ContainerContext'; +import StruktureringAvDokumentasjon from '../StruktureringAvDokumentasjon'; + +const dokumentoversiktEndpoint = 'mockedDokumentoversiktEndpoint'; +const dokumentoversiktMock = { + dokumenter: [], +}; + +const httpErrorHandlerMock = () => null; + +const contextWrapper = ui => + render( + + {ui} + , + ); + +const navigerTilNesteStegMock = { + fn: () => null, +}; + +const getFunctionThatReturnsAResolvedPromise = data => () => + new Promise(resolve => { + resolve(data); + }); + +const sykdomsstegFerdigStatusMock = { + fn: getFunctionThatReturnsAResolvedPromise({ kanLøseAksjonspunkt: true }), +}; + +const sykdomsstegDokumentUferdigStatusMock = { + fn: getFunctionThatReturnsAResolvedPromise({ kanLøseAksjonspunkt: false, harUklassifiserteDokumenter: true }), +}; + +const sykdomsstegKTPUferdigStatusMock = { + fn: getFunctionThatReturnsAResolvedPromise({ + kanLøseAksjonspunkt: false, + harUklassifiserteDokumenter: false, + manglerVurderingAvKontinuerligTilsynOgPleie: true, + }), +}; + +const renderVilkårsvurderingComponent = ( + kanLøseAksjonspunkt?: boolean, + harUklassifiserteDokumenter?: boolean, + manglerKTPVurdering?: boolean, +) => { + let hentSykdomsstegStatusMock = sykdomsstegFerdigStatusMock; + if (kanLøseAksjonspunkt) { + hentSykdomsstegStatusMock = sykdomsstegFerdigStatusMock; + } + if (harUklassifiserteDokumenter) { + hentSykdomsstegStatusMock = sykdomsstegDokumentUferdigStatusMock; + } + if (manglerKTPVurdering) { + hentSykdomsstegStatusMock = sykdomsstegKTPUferdigStatusMock; + } + + return contextWrapper( + , + ); +}; + +describe('StruktureringAvDokumentasjon', () => { + let httpGetSpy = null; + + beforeAll(() => { + httpGetSpy = jest.spyOn(httpUtils, 'get'); + }); + + const mockResolvedGetApiCallOnce = data => { + httpGetSpy.mockImplementationOnce( + () => + new Promise(resolve => { + resolve(data); + }), + ); + }; + + const mockRejectedGetApiCallOnce = () => { + httpGetSpy.mockImplementationOnce( + () => + new Promise((resolve, reject) => { + reject(); + }), + ); + }; + + describe('when dokumentoversikt-http call is successful and data is in the expected format', () => { + it('should render vurderingsoversikt presentation properly during and after the data has been fetched', async () => { + mockResolvedGetApiCallOnce(dokumentoversiktMock); + const { getByText } = renderVilkårsvurderingComponent(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + await waitFor(() => expect(getByText(/Ingen dokumenter å vise/i)).toBeInTheDocument()); + }); + }); + + describe('when dokumentoversikt-http call is successful, but data is not in the expected format', () => { + it('should render vurderingsoversikt presentation properly after error handling', async () => { + mockResolvedGetApiCallOnce({}); + const { getByText } = renderVilkårsvurderingComponent(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + await waitFor(() => expect(getByText(/Noe gikk galt, vennligst prøv igjen senere/i)).toBeInTheDocument()); + }); + }); + + describe('when dokumentoversikt-http call fails', () => { + it('should render vurderingsoversikt properly after error handling', async () => { + mockRejectedGetApiCallOnce(); + const { getByText } = renderVilkårsvurderingComponent(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + await waitFor(() => expect(getByText(/Noe gikk galt, vennligst prøv igjen senere/i)).toBeInTheDocument()); + }); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturert-dokument-detaljer/StrukturertDokumentDetaljer.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturert-dokument-detaljer/StrukturertDokumentDetaljer.tsx" new file mode 100644 index 0000000000..098804c158 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturert-dokument-detaljer/StrukturertDokumentDetaljer.tsx" @@ -0,0 +1,144 @@ +import { Alert, Link } from '@navikt/ds-react'; +import { Box, DetailView, LabelledContent, LinkButton, Margin } from '@navikt/ft-plattform-komponenter'; +import { prettifyDateString } from '@fpsak-frontend/utils'; +import React from 'react'; +import FagsakYtelseType from '../../../constants/FagsakYtelseType'; +import LinkRel from '../../../constants/LinkRel'; +import Dokument, { dokumentLabel, Dokumenttype } from '../../../types/Dokument'; +import { findLinkByRel } from '../../../util/linkUtils'; +import ContainerContext from '../../context/ContainerContext'; +import DokumentKnapp from '../dokument-knapp/DokumentKnapp'; +import Duplikatliste from '../duplikatliste/Duplikatliste'; +import WriteAccessBoundContent from '../write-access-bound-content/WriteAccessBoundContent'; +import styles from './strukturertDokumentDetaljer.css'; + +interface StrukturertDokumentDetaljerProps { + dokument: Dokument; + onEditDokumentClick: () => void; + strukturerteDokumenter: Dokument[]; + onRemoveDuplikat: () => void; +} + +const renderDokumenttypeLabel = (fagsakYtelseType: FagsakYtelseType) => { + if (fagsakYtelseType === FagsakYtelseType.OPPLÆRINGSPENGER) { + return 'Inneholder dokumentet medisinske opplysninger eller dokumentasjon av opplæring?'; + } + + return 'Inneholder dokumentet medisinske opplysninger?'; +}; + +const renderDokumenttypeContent = (dokumenttype: Dokumenttype, fagsakYtelseType: FagsakYtelseType) => { + if (dokumenttype === Dokumenttype.LEGEERKLÆRING) { + return fagsakYtelseType === FagsakYtelseType.PLEIEPENGER_SLUTTFASE ? ( + Ja, dokumentet inneholder medisinske opplysninger + ) : ( + Ja, legeerklæring fra sykehus/spesialisthelsetjenesten + ); + } + if (dokumenttype === Dokumenttype.LEGEERKLÆRING_MED_DOKUMENTASJON_AV_OPPLÆRING) { + return Ja, både legeerklæring og dokumentasjon av opplæring; + } + if (dokumenttype === Dokumenttype.EPIKRISE) { + return Ja, epikrise; + } + if (dokumenttype === Dokumenttype.LEGEERKLÆRING_ANNEN) { + return Ja, legeerklæring fra lege eller helseinstitusjon; + } + if (dokumenttype === Dokumenttype.DOKUMENTASJON_AV_OPPLÆRING) { + return Ja, dokumentasjon av opplæring; + } + if (dokumenttype === Dokumenttype.ANDRE_MEDISINSKE_OPPLYSNINGER) { + return Ja, andre medisinske opplysninger (f.eks. legeerklæring fra fastlege, uttalelse fra psykolog); + } + if (dokumenttype === Dokumenttype.MANGLER_MEDISINSKE_OPPLYSNINGER) { + if (fagsakYtelseType === FagsakYtelseType.OPPLÆRINGSPENGER) { + return Nei, dokumentet inneholder ikke medisinske opplysninger eller dokumentasjon av opplæring; + } + return Dokumentet inneholder ikke medisinske opplysninger; + } + return null; +}; + +const StrukturertDokumentDetaljer = ({ + dokument, + onEditDokumentClick, + strukturerteDokumenter, + onRemoveDuplikat, +}: StrukturertDokumentDetaljerProps): JSX.Element => { + const { fagsakYtelseType } = React.useContext(ContainerContext); + const { type, datert, links, duplikater, duplikatAvId } = dokument; + const harDuplikater = duplikater?.length > 0; + const dokumentinnholdLink = findLinkByRel(LinkRel.DOKUMENT_INNHOLD, links); + const getDokumentDuplikater = () => + strukturerteDokumenter.filter(strukturertDokument => strukturertDokument.duplikatAvId === dokument.id); + + const getOriginaltDokument = () => { + const originaltDokument = strukturerteDokumenter.find( + strukturertDokument => strukturertDokument.id === duplikatAvId, + ); + const dokumentLink = findLinkByRel(LinkRel.DOKUMENT_INNHOLD, originaltDokument.links); + + return ( + + {`${dokumentLabel[originaltDokument.type]} - ${prettifyDateString(originaltDokument.datert)}`} + + ); + }; + + return ( + ( + ( + + Endre dokument + + )} + /> + )} + > + {harDuplikater && ( + + + Det finnes ett eller flere duplikater av dette dokumentet. + + + )} + {duplikatAvId && ( + + + Dokumentet er et duplikat. + + + )} + + + + + + + + + + {harDuplikater && ( + + } + /> + + )} + {duplikatAvId && ( + + + + )} + + ); +}; + +export default StrukturertDokumentDetaljer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturert-dokument-detaljer/strukturertDokumentDetaljer.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturert-dokument-detaljer/strukturertDokumentDetaljer.css" new file mode 100644 index 0000000000..9411969476 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturert-dokument-detaljer/strukturertDokumentDetaljer.css" @@ -0,0 +1,3 @@ +.endreLink { + margin-left: 1rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturet-dokument-element/StrukturertDokumentElement.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturet-dokument-element/StrukturertDokumentElement.tsx" new file mode 100644 index 0000000000..ea95355742 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturet-dokument-element/StrukturertDokumentElement.tsx" @@ -0,0 +1,75 @@ +import { prettifyDateString } from '@fpsak-frontend/utils'; +import { + ContentWithTooltip, + DuplicateDocumentsIcon, + GreenCheckIconFilled, + OnePersonIconGray, + OnePersonOutlineGray, +} from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import Dokument, { dokumentLabel } from '../../../types/Dokument'; +import styles from './strukturertDokumentElement.css'; + +interface StrukturertDokumentElementProps { + dokument: Dokument; +} + +const StrukturertDokumentElement = ({ + dokument: { navn, datert, type, annenPartErKilde, duplikater }, +}: StrukturertDokumentElementProps): JSX.Element => { + const harDuplikater = duplikater?.length > 0; + + const getDokumenttype = () => { + if (dokumentLabel[type]) { + return dokumentLabel[type]; + } + return navn; + }; + + const parterLabel = () => { + if (annenPartErKilde) { + return ( + + + + ); + } + return ( + + + + ); + }; + + return ( +
    + Status + + + +
    +

    + Type + {getDokumenttype()} +

    + + Datert + {prettifyDateString(datert)} + + + Part + {parterLabel()} + + {harDuplikater && ( + + + + + + )} +
    +
    + ); +}; + +export default StrukturertDokumentElement; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturet-dokument-element/strukturertDokumentElement.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturet-dokument-element/strukturertDokumentElement.css" new file mode 100644 index 0000000000..fb19040893 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/strukturet-dokument-element/strukturertDokumentElement.css" @@ -0,0 +1,52 @@ +.strukturertDokumentElement { + display: flex; + align-items: center; + cursor: pointer; + color: #262626; +} + +.strukturertDokumentElement__texts { + margin-left: 2rem; + display: flex; + align-items: center; +} + +.strukturertDokumentElement__texts__type { + display: inline-block; + min-width: 170px; + color: #0067c5; + text-decoration: underline; + margin: 0; +} + +.strukturertDokumentElement__texts__date { + margin-left: 1rem; +} + +.strukturertDokumentElement__texts__part { + margin-left: 1.375rem; + margin-bottom: -0.25rem; + display: inline-block; +} + +.strukturertDokumentElement__texts__document { + margin-left: 1.5rem; + margin-bottom: -0.125rem; +} + +.strukturertDokumentElement__texts__document svg { + width: 1.125rem; + height: 1.375rem; + stroke: none; +} + +.strukturertDokumentElement .visuallyHidden { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ustrukturert-dokument-element/UstrukturertDokumentElement.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ustrukturert-dokument-element/UstrukturertDokumentElement.tsx" new file mode 100644 index 0000000000..b4b70b4c08 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ustrukturert-dokument-element/UstrukturertDokumentElement.tsx" @@ -0,0 +1,59 @@ +import { prettifyDateString } from '@fpsak-frontend/utils'; +import { + ContentWithTooltip, + OnePersonIconGray, + OnePersonOutlineGray, + WarningIcon, +} from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import { Dokument, dokumentLabel } from '../../../types/Dokument'; +import styles from './ustrukturertDokumentElement.css'; + +interface UstrukturertDokumentElementProps { + dokument: Dokument; +} + +const UstrukturertDokumentElement = ({ + dokument: { datert, mottattDato, annenPartErKilde }, +}: UstrukturertDokumentElementProps): JSX.Element => { + const parterLabel = () => { + if (annenPartErKilde) { + return ( + + + + ); + } + return ( + + + + ); + }; + + return ( +
    + + + +
    +

    + Type + {dokumentLabel.UKLASSIFISERT} +

    + + Datert + + {`${prettifyDateString(datert || mottattDato)}*`} + + + + Part + {parterLabel()} + +
    +
    + ); +}; + +export default UstrukturertDokumentElement; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ustrukturert-dokument-element/ustrukturertDokumentElement.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ustrukturert-dokument-element/ustrukturertDokumentElement.css" new file mode 100644 index 0000000000..108d0c02ed --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/ustrukturert-dokument-element/ustrukturertDokumentElement.css" @@ -0,0 +1,51 @@ +.ustrukturertDokumentElement { + display: flex; + align-items: center; + cursor: pointer; +} + +.ustrukturertDokumentElement__texts { + margin-left: 2rem; + display: flex; + align-items: center; +} + +.ustrukturertDokumentElement__texts__type { + display: inline-block; + min-width: 170px; + color: #0067c5; + text-decoration: underline; + margin: 0; +} + +.ustrukturertDokumentElement__texts__date { + margin-left: 1rem; +} + +.ustrukturertDokumentElement__texts__part { + margin-left: 1rem; + margin-bottom: -0.25rem; + display: inline-block; +} + +.ustrukturertDokumentElement__texts__document { + margin-left: 1.5rem; + margin-bottom: -0.125rem; +} + +.ustrukturertDokumentElement__texts__document svg { + width: 1.125rem; + height: 1.375rem; + stroke: none; +} + +.ustrukturertDokumentElement .visuallyHidden { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/utest\303\245ende-endringer-melding/Utest\303\245endeEndringerMelding.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/utest\303\245ende-endringer-melding/Utest\303\245endeEndringerMelding.tsx" new file mode 100644 index 0000000000..57e9661b79 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/utest\303\245ende-endringer-melding/Utest\303\245endeEndringerMelding.tsx" @@ -0,0 +1,36 @@ +import { Alert, BodyShort, Button } from '@navikt/ds-react'; +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import ContainerContext from '../../context/ContainerContext'; + +const UteståendeEndringerMelding = (): JSX.Element => { + const { onFinished } = React.useContext(ContainerContext); + const [isSubmitting, setIsSubmitting] = React.useState(false); + + return ( + + + + OBS! Det er gjort endringer i sykdomssteget. For at endringene som er gjort skal bli tatt med i behandlingen, + trykk på Fortsett. + + + + + + + ); +}; + +export default UteståendeEndringerMelding; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilkarsvurdering-av-livets-sluttfase/VilkarsvurderingAvLivetsSluttfase.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilkarsvurdering-av-livets-sluttfase/VilkarsvurderingAvLivetsSluttfase.tsx" new file mode 100644 index 0000000000..a73bdc6285 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilkarsvurdering-av-livets-sluttfase/VilkarsvurderingAvLivetsSluttfase.tsx" @@ -0,0 +1,192 @@ +import { get, Period } from '@fpsak-frontend/utils'; +import { NavigationWithDetailView, PageContainer, Box, Margin } from '@navikt/ft-plattform-komponenter'; +import React, { useMemo } from 'react'; +import Step, { livetsSluttfaseSteg, StepId } from '../../../types/Step'; +import SykdomsstegStatusResponse from '../../../types/SykdomsstegStatusResponse'; +import Vurderingselement from '../../../types/Vurderingselement'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import { finnNesteStegForLivetsSluttfase } from '../../../util/statusUtils'; +import ContainerContext from '../../context/ContainerContext'; +import Vurderingsnavigasjon from '../vurderingsnavigasjon/Vurderingsnavigasjon'; +import ActionType from './actionTypes'; +import vilkårsvurderingReducer from './reducer'; +import Vurderingsdetaljer from '../vurderingsdetaljer/Vurderingsdetaljer'; + +import VurderingsoversiktSluttfaseMessages from '../vurderingsoversikt-sluttfase-messages/VurderingsoversiktSluttfaseMessages'; +import BehandlingType from '../../../constants/BehandlingType'; +import FagsakYtelseType from '../../../constants/FagsakYtelseType'; + +interface VilkårsvurderingAvLivetsSluttfaseProps { + navigerTilNesteSteg: (steg: Step, ikkeMarkerSteg?: boolean) => void; + hentSykdomsstegStatus: () => Promise; + sykdomsstegStatus: SykdomsstegStatusResponse; +} + +const VilkårsvurderingAvLivetsSluttfase = ({ + navigerTilNesteSteg, + hentSykdomsstegStatus, + sykdomsstegStatus, +}: VilkårsvurderingAvLivetsSluttfaseProps): JSX.Element => { + const { endpoints, httpErrorHandler, fagsakYtelseType, behandlingType } = React.useContext(ContainerContext); + const controller = useMemo(() => new AbortController(), []); + + const [state, dispatch] = React.useReducer(vilkårsvurderingReducer, { + visVurderingDetails: false, + isLoading: true, + vurderingsoversikt: null, + valgtVurderingselement: null, + skalViseRadForNyVurdering: false, + vurderingsoversiktFeilet: false, + }); + + const { + vurderingsoversikt, + isLoading, + visVurderingDetails, + valgtVurderingselement, + skalViseRadForNyVurdering, + vurderingsoversiktFeilet, + } = state; + + const { manglerGodkjentLegeerklæring } = sykdomsstegStatus; + const harGyldigSignatur = !manglerGodkjentLegeerklæring; + + const getVurderingsoversikt = () => + get(endpoints.vurderingsoversiktLivetsSluttfase, httpErrorHandler, { + signal: controller.signal, + }); + + const visVurderingsoversikt = (nyVurderingsoversikt: Vurderingsoversikt) => { + dispatch({ type: ActionType.VIS_VURDERINGSOVERSIKT, vurderingsoversikt: nyVurderingsoversikt }); + }; + + const handleError = () => { + dispatch({ type: ActionType.VURDERINGSOVERSIKT_FEILET }); + }; + + const visNyVurderingForm = (resterendeVurderingsperioder?: Period[]) => { + dispatch({ + type: ActionType.VIS_NY_VURDERING_FORM, + resterendeVurderingsperioder, + }); + }; + + const åpneFørstePeriodeSomMåBehandles = (nyVurderingsoversikt: Vurderingsoversikt) => { + const harEnPeriodeSomMåBehandles = nyVurderingsoversikt?.resterendeVurderingsperioder?.length > 0; + if (harEnPeriodeSomMåBehandles) { + visNyVurderingForm(nyVurderingsoversikt.resterendeVurderingsperioder); + } + }; + + React.useEffect(() => { + let isMounted = true; + getVurderingsoversikt() + .then(vurderingsoversiktData => { + if (isMounted) { + const nyVurderingsoversikt = new Vurderingsoversikt(vurderingsoversiktData); + visVurderingsoversikt(nyVurderingsoversikt); + åpneFørstePeriodeSomMåBehandles(nyVurderingsoversikt); + } + }) + .catch(handleError); + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + const velgVurderingselement = (nyValgtVurderingselement: Vurderingselement) => { + dispatch({ type: ActionType.VELG_VURDERINGSELEMENT, valgtVurderingselement: nyValgtVurderingselement }); + }; + + const oppdaterVurderingsoversikt = () => { + dispatch({ type: ActionType.PENDING }); + getVurderingsoversikt().then(vurderingsoversiktData => { + const nyVurderingsoversikt = new Vurderingsoversikt(vurderingsoversiktData); + visVurderingsoversikt(nyVurderingsoversikt); + }); + }; + + const onAvbryt = () => { + dispatch({ + type: ActionType.AVBRYT_FORM, + }); + }; + + const onVurderingLagret = () => { + dispatch({ type: ActionType.PENDING }); + hentSykdomsstegStatus() + .then(status => { + const nesteSteg = finnNesteStegForLivetsSluttfase(status); + if (nesteSteg === livetsSluttfaseSteg || nesteSteg === null) { + oppdaterVurderingsoversikt(); + } else if (nesteSteg !== null) { + navigerTilNesteSteg(nesteSteg); + } + }) + .catch(handleError); + }; + + const setMargin = () => { + if (vurderingsoversikt.harPerioderSomSkalVurderes() || !harGyldigSignatur) { + return Margin.medium; + } + return null; + }; + + const skalViseOpprettVurderingKnapp = () => { + if ( + fagsakYtelseType === FagsakYtelseType.PLEIEPENGER_SLUTTFASE && + BehandlingType.FORSTEGANGSSOKNAD === behandlingType + ) + return false; + + return !vurderingsoversikt?.harPerioderSomSkalVurderes() && + !skalViseRadForNyVurdering && + harGyldigSignatur && + fagsakYtelseType === FagsakYtelseType.PLEIEPENGER_SLUTTFASE + ? behandlingType !== BehandlingType.FORSTEGANGSSOKNAD + : true; + }; + + const skalViseNyVurderingForm = visVurderingDetails && !valgtVurderingselement; + + return ( + + + {vurderingsoversikt?.harPerioderÅVise() && ( + + ( + + )} + showDetailSection={visVurderingDetails} + detailSection={() => ( + + )} + /> + + )} + + ); +}; + +export default VilkårsvurderingAvLivetsSluttfase; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilkarsvurdering-av-livets-sluttfase/actionTypes.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilkarsvurdering-av-livets-sluttfase/actionTypes.ts" new file mode 100644 index 0000000000..3b84a5746b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilkarsvurdering-av-livets-sluttfase/actionTypes.ts" @@ -0,0 +1,10 @@ +enum ActionType { + VIS_VURDERINGSOVERSIKT = 'visVurderingsoversikt', + VIS_NY_VURDERING_FORM = 'visNyVurderingForm', + VELG_VURDERINGSELEMENT = 'velgVurderingselement', + PENDING = 'pending', + VURDERINGSOVERSIKT_FEILET = 'vurderingsoversiktFeilet', + AVBRYT_FORM = 'avbrytForm', +} + +export default ActionType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilkarsvurdering-av-livets-sluttfase/reducer.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilkarsvurdering-av-livets-sluttfase/reducer.ts" new file mode 100644 index 0000000000..a260350d3b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilkarsvurdering-av-livets-sluttfase/reducer.ts" @@ -0,0 +1,72 @@ +import { Period } from '@fpsak-frontend/utils'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import ActionType from './actionTypes'; +import Vurderingselement from '../../../types/Vurderingselement'; + +interface State { + visVurderingDetails: boolean; + isLoading: boolean; + vurderingsoversikt: Vurderingsoversikt; + valgtVurderingselement: Vurderingselement; + skalViseRadForNyVurdering: boolean; + vurderingsoversiktFeilet: boolean; +} + +interface Action { + type: ActionType; + vurderingsoversikt?: Vurderingsoversikt; + valgtVurderingselement?: Vurderingselement; + resterendeVurderingsperioder?: Period[]; +} + +const vilkårsvurderingReducer = (state: State, action: Action): State => { + switch (action.type) { + case ActionType.VIS_VURDERINGSOVERSIKT: { + return { + ...state, + vurderingsoversikt: action.vurderingsoversikt, + isLoading: false, + visVurderingDetails: false, + skalViseRadForNyVurdering: false, + vurderingsoversiktFeilet: false, + }; + } + case ActionType.VURDERINGSOVERSIKT_FEILET: { + return { + ...state, + isLoading: false, + vurderingsoversiktFeilet: true, + }; + } + case ActionType.VIS_NY_VURDERING_FORM: + return { + ...state, + valgtVurderingselement: null, + visVurderingDetails: true, + skalViseRadForNyVurdering: !action.resterendeVurderingsperioder, + }; + case ActionType.VELG_VURDERINGSELEMENT: + return { + ...state, + valgtVurderingselement: action.valgtVurderingselement, + visVurderingDetails: true, + }; + case ActionType.PENDING: + return { + ...state, + isLoading: true, + vurderingsoversiktFeilet: false, + }; + case ActionType.AVBRYT_FORM: + return { + ...state, + visVurderingDetails: false, + valgtVurderingselement: null, + skalViseRadForNyVurdering: false, + }; + default: + return state; + } +}; + +export default vilkårsvurderingReducer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/Vilk\303\245rsvurderingAvTilsynOgPleie.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/Vilk\303\245rsvurderingAvTilsynOgPleie.tsx" new file mode 100644 index 0000000000..e4b71b99a1 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/Vilk\303\245rsvurderingAvTilsynOgPleie.tsx" @@ -0,0 +1,180 @@ +import { get, Period } from '@fpsak-frontend/utils'; +import { NavigationWithDetailView, PageContainer, Box, Margin } from '@navikt/ft-plattform-komponenter'; +import React, { useMemo } from 'react'; +import Step, { StepId, tilsynOgPleieSteg, toOmsorgspersonerSteg } from '../../../types/Step'; +import SykdomsstegStatusResponse from '../../../types/SykdomsstegStatusResponse'; +import Vurderingselement from '../../../types/Vurderingselement'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import { finnNesteStegForPleiepenger } from '../../../util/statusUtils'; +import ContainerContext from '../../context/ContainerContext'; +import Vurderingsnavigasjon from '../vurderingsnavigasjon/Vurderingsnavigasjon'; +import VurderingsoversiktMessages from '../vurderingsoversikt-messages/VurderingsoversiktMessages'; +import ActionType from './actionTypes'; +import vilkårsvurderingReducer from './reducer'; +import Vurderingsdetaljer from '../vurderingsdetaljer/Vurderingsdetaljer'; + +interface VilkårsvurderingAvTilsynOgPleieProps { + navigerTilNesteSteg: (steg: Step, ikkeMarkerSteg?: boolean) => void; + hentSykdomsstegStatus: () => Promise; + sykdomsstegStatus: SykdomsstegStatusResponse; +} + +const VilkårsvurderingAvTilsynOgPleie = ({ + navigerTilNesteSteg, + hentSykdomsstegStatus, + sykdomsstegStatus, +}: VilkårsvurderingAvTilsynOgPleieProps): JSX.Element => { + const { endpoints, httpErrorHandler } = React.useContext(ContainerContext); + const controller = useMemo(() => new AbortController(), []); + + const [state, dispatch] = React.useReducer(vilkårsvurderingReducer, { + visVurderingDetails: false, + isLoading: true, + vurderingsoversikt: null, + valgtVurderingselement: null, + skalViseRadForNyVurdering: false, + vurderingsoversiktFeilet: false, + }); + + const { + vurderingsoversikt, + isLoading, + visVurderingDetails, + valgtVurderingselement, + skalViseRadForNyVurdering, + vurderingsoversiktFeilet, + } = state; + + const { manglerGodkjentLegeerklæring } = sykdomsstegStatus; + const harGyldigSignatur = !manglerGodkjentLegeerklæring; + + const getVurderingsoversikt = () => + get(endpoints.vurderingsoversiktKontinuerligTilsynOgPleie, httpErrorHandler, { + signal: controller.signal, + }); + + const visVurderingsoversikt = (nyVurderingsoversikt: Vurderingsoversikt) => { + dispatch({ type: ActionType.VIS_VURDERINGSOVERSIKT, vurderingsoversikt: nyVurderingsoversikt }); + }; + + const handleError = () => { + dispatch({ type: ActionType.VURDERINGSOVERSIKT_FEILET }); + }; + + const visNyVurderingForm = (resterendeVurderingsperioder?: Period[]) => { + dispatch({ + type: ActionType.VIS_NY_VURDERING_FORM, + resterendeVurderingsperioder, + }); + }; + + const åpneFørstePeriodeSomMåBehandles = (nyVurderingsoversikt: Vurderingsoversikt) => { + const harEnPeriodeSomMåBehandles = nyVurderingsoversikt?.resterendeVurderingsperioder?.length > 0; + if (harEnPeriodeSomMåBehandles) { + visNyVurderingForm(nyVurderingsoversikt.resterendeVurderingsperioder); + } + }; + + React.useEffect(() => { + let isMounted = true; + getVurderingsoversikt() + .then(vurderingsoversiktData => { + if (isMounted) { + const nyVurderingsoversikt = new Vurderingsoversikt(vurderingsoversiktData); + visVurderingsoversikt(nyVurderingsoversikt); + åpneFørstePeriodeSomMåBehandles(nyVurderingsoversikt); + } + }) + .catch(handleError); + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + const velgVurderingselement = (nyValgtVurderingselement: Vurderingselement) => { + dispatch({ type: ActionType.VELG_VURDERINGSELEMENT, valgtVurderingselement: nyValgtVurderingselement }); + }; + + const oppdaterVurderingsoversikt = () => { + dispatch({ type: ActionType.PENDING }); + getVurderingsoversikt().then(vurderingsoversiktData => { + const nyVurderingsoversikt = new Vurderingsoversikt(vurderingsoversiktData); + visVurderingsoversikt(nyVurderingsoversikt); + }); + }; + + const onAvbryt = () => { + dispatch({ + type: ActionType.AVBRYT_FORM, + }); + }; + + const onVurderingLagret = () => { + dispatch({ type: ActionType.PENDING }); + hentSykdomsstegStatus() + .then(status => { + if (status.kanLøseAksjonspunkt) { + navigerTilNesteSteg(toOmsorgspersonerSteg, true); + return; + } + + const nesteSteg = finnNesteStegForPleiepenger(status); + if (nesteSteg === tilsynOgPleieSteg || nesteSteg === null) { + oppdaterVurderingsoversikt(); + } else { + const ikkeMarkerSteg = nesteSteg === toOmsorgspersonerSteg && !status.manglerVurderingAvToOmsorgspersoner; + navigerTilNesteSteg(nesteSteg, ikkeMarkerSteg); + } + }) + .catch(handleError); + }; + + const setMargin = () => { + if (vurderingsoversikt.harPerioderSomSkalVurderes() || !harGyldigSignatur) { + return Margin.medium; + } + return null; + }; + + const skalViseOpprettVurderingKnapp = + !vurderingsoversikt?.harPerioderSomSkalVurderes() && !skalViseRadForNyVurdering && harGyldigSignatur; + + const skalViseNyVurderingForm = visVurderingDetails && !valgtVurderingselement; + + return ( + + + {vurderingsoversikt?.harPerioderÅVise() && ( + + ( + + )} + showDetailSection={visVurderingDetails} + detailSection={() => ( + + )} + /> + + )} + + ); +}; + +export default VilkårsvurderingAvTilsynOgPleie; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/__tests__/Vilk\303\245rsvurderingAvTilsynOgPleie.spec.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/__tests__/Vilk\303\245rsvurderingAvTilsynOgPleie.spec.tsx" new file mode 100644 index 0000000000..a5539985e8 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/__tests__/Vilk\303\245rsvurderingAvTilsynOgPleie.spec.tsx" @@ -0,0 +1,313 @@ +/* eslint-disable max-len */ +import React from 'react'; +import { httpUtils } from '@fpsak-frontend/utils'; +import { render, fireEvent, waitFor, screen } from '@testing-library/react'; +import VilkårsvurderingAvTilsynOgPleie from '../VilkårsvurderingAvTilsynOgPleie'; +import ContainerContext from '../../../context/ContainerContext'; +import VurderingContext from '../../../context/VurderingContext'; +import Vurderingstype from '../../../../types/Vurderingstype'; +import { dokumentSteg, toOmsorgspersonerSteg } from '../../../../types/Step'; + +const vurderingsoversiktEndpoint = 'vurderingsoversikt-mock'; +const vurderingsopprettelseEndpoint = 'vurderingsopprettelse-mock'; + +const httpErrorHandlerMock = () => null; +const abortControllerMock = { signal: new AbortController().signal }; + +const vurderingsoversiktMock = { + perioderSomKanVurderes: [], + resterendeVurderingsperioder: [], + resterendeValgfrieVurderingsperioder: [], + søknadsperioderTilBehandling: [], + vurderingselementer: [], + links: [ + { + rel: 'sykdom-vurdering-opprettelse', + href: vurderingsopprettelseEndpoint, + requestPayload: { + behandlingUuid: 'foo', + }, + }, + ], +} as any; + +window.scroll = () => null; + +const contextWrapper = ui => + render( + + + {ui} + + , + ); + +const navigerTilNesteStegMock = { + fn: () => null, +}; + +const getFunctionThatReturnsAResolvedPromise = data => () => + new Promise(resolve => { + resolve(data); + }); + +const sykdomsstegFerdigStatusMock = { + fn: getFunctionThatReturnsAResolvedPromise({ kanLøseAksjonspunkt: true }), +}; + +const sykdomsstegDokumentUferdigStatusMock = { + fn: getFunctionThatReturnsAResolvedPromise({ kanLøseAksjonspunkt: false, harUklassifiserteDokumenter: true }), +}; + +const sykdomsstegKTPUferdigStatusMock = { + fn: getFunctionThatReturnsAResolvedPromise({ + kanLøseAksjonspunkt: false, + harUklassifiserteDokumenter: false, + manglerVurderingAvKontinuerligTilsynOgPleie: true, + }), +}; + +const renderVilkårsvurderingComponent = ( + kanLøseAksjonspunkt?: boolean, + harUklassifiserteDokumenter?: boolean, + manglerKTPVurdering?: boolean, +) => { + let hentSykdomsstegStatusMock = sykdomsstegFerdigStatusMock; + if (kanLøseAksjonspunkt) { + hentSykdomsstegStatusMock = sykdomsstegFerdigStatusMock; + } + if (harUklassifiserteDokumenter) { + hentSykdomsstegStatusMock = sykdomsstegDokumentUferdigStatusMock; + } + if (manglerKTPVurdering) { + hentSykdomsstegStatusMock = sykdomsstegKTPUferdigStatusMock; + } + + return contextWrapper( + , + ); +}; + +describe('VilkårsvurderingAvTilsynOgPleie', () => { + let httpGetSpy = null; + let httpPostSpy = null; + + let sykdomsstegFerdigStatusSpy = null; + let sykdomsstegDokumentUferdigStatusSpy = null; + let sykdomsstegKTPUferdigStatusSpy = null; + + let navigerTilNesteStegSpy = null; + + beforeAll(() => { + httpGetSpy = jest.spyOn(httpUtils, 'get'); + httpPostSpy = jest.spyOn(httpUtils, 'post'); + + sykdomsstegFerdigStatusSpy = jest.spyOn(sykdomsstegFerdigStatusMock, 'fn'); + sykdomsstegDokumentUferdigStatusSpy = jest.spyOn(sykdomsstegDokumentUferdigStatusMock, 'fn'); + sykdomsstegKTPUferdigStatusSpy = jest.spyOn(sykdomsstegKTPUferdigStatusMock, 'fn'); + + navigerTilNesteStegSpy = jest.spyOn(navigerTilNesteStegMock, 'fn'); + }); + + const mockResolvedGetApiCallOnce = data => { + httpGetSpy.mockImplementationOnce( + () => + new Promise(resolve => { + resolve(data); + }), + ); + }; + + const mockRejectedGetApiCallOnce = () => { + httpGetSpy.mockImplementationOnce( + () => + new Promise((resolve, reject) => { + reject(); + }), + ); + }; + + const mockResolvedGetApiCall = data => { + httpGetSpy.mockImplementation( + () => + new Promise(resolve => { + resolve(data); + }), + ); + }; + + const mockResolvedPostApiCall = data => { + httpPostSpy.mockImplementation( + () => + new Promise(resolve => { + resolve(data); + }), + ); + }; + + describe('when vurderingsoversikt-http call is successful and data is in the expected format', () => { + it('should render vurderingsoversikt presentation properly during and after the data has been fetched', async () => { + mockResolvedGetApiCallOnce(vurderingsoversiktMock); + const { getByText } = renderVilkårsvurderingComponent(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + await waitFor(() => expect(getByText(/Ingen perioder å vurdere/i)).toBeInTheDocument()); + }); + }); + + describe('when vurderingsoversikt-http call is successful, but data is not in the expected format', () => { + it('should render vurderingsoversikt presentation properly after error handling', async () => { + mockResolvedGetApiCallOnce({}); + const { getByText } = renderVilkårsvurderingComponent(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + await waitFor(() => expect(getByText(/Noe gikk galt, vennligst prøv igjen senere/i)).toBeInTheDocument()); + }); + }); + + describe('when vurderingsoversikt-http call fails', () => { + it('should render vurderingsoversikt properly after error handling', async () => { + mockRejectedGetApiCallOnce(); + const { getByText } = renderVilkårsvurderingComponent(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + await waitFor(() => expect(getByText(/Noe gikk galt, vennligst prøv igjen senere/i)).toBeInTheDocument()); + }); + }); + + describe('when there are periods that need to be considered', () => { + beforeEach(() => { + mockResolvedGetApiCallOnce({ + ...vurderingsoversiktMock, + resterendeVurderingsperioder: [{ fom: '2028-01-01', tom: '2028-01-01' }], + }); + }); + + it('should open form for periods that need consideration by default', async () => { + renderVilkårsvurderingComponent(); + await waitFor(() => { + expect(screen.getByText(/Perioden må vurderes/i)).toBeInTheDocument(); + expect(screen.getByText(/Vurder behov for tilsyn og pleie for 01.01.2028 - 01.01.2028/i)).toBeInTheDocument(); + expect(screen.queryByText('Ny vurdering')).toBeNull(); + }); + }); + }); + + describe('when there are no periods that need to be considered', () => { + beforeEach(() => { + mockResolvedGetApiCallOnce({ + ...vurderingsoversiktMock, + vurderingselementer: [{ periode: { fom: '2028-01-01', tom: '2028-01-01' } }], + }); + }); + + it('should open vurdering-form when Ny vurdering-button is clicked, and form should be closeable by clicking Avbryt button', async () => { + const res = renderVilkårsvurderingComponent(); + expect(res.queryByText(/Vurdering av tilsyn og pleie/)).toBeNull(); + fireEvent.click(await res.findByText('Ny vurdering')); + expect(res.getByText(/Vurdering av tilsyn og pleie/i)).toBeVisible(); + const avbrytKnapp = await res.findByText(/^Avbryt$/i); + fireEvent.click(avbrytKnapp); + expect(res.queryByText(/Vurdering av tilsyn og pleie/)).toBeNull(); + }); + }); + + describe('when vurdering form is submitted and it has been saved successfully', () => { + beforeEach(() => { + mockResolvedGetApiCall({ + ...vurderingsoversiktMock, + resterendeVurderingsperioder: [{ fom: '2028-01-01', tom: '2028-01-01' }], + perioderSomKanVurderes: [{ fom: '2028-01-01', tom: '2028-01-01' }], + }); + }); + + afterEach(() => { + httpGetSpy.mockClear(); + httpPostSpy.mockClear(); + navigerTilNesteStegSpy.mockClear(); + }); + + it('should get new sykdomsstatus after successfully posting vurdering, and when it responds with kanLøseAksjonspunkt=true, it should navigate user to to omsorgspersoner', async () => { + renderVilkårsvurderingComponent(true); + await waitFor(async () => { + const textarea = screen.getByLabelText(/Gjør en vurdering av/i); + fireEvent.change(textarea, { target: { value: 'Foo Bar Baz' } }); + expect(textarea).toHaveValue('Foo Bar Baz'); + + const radio = screen.getByLabelText('Ja'); + fireEvent.click(radio); + }); + + const submitButton = screen.getByText('Bekreft'); + mockResolvedPostApiCall({ perioderMedEndringer: [] }); + fireEvent.click(submitButton); + + await waitFor(() => { + // one post with dryRun=true, another with dryRun=false + expect(httpPostSpy).toHaveBeenCalledTimes(2); + expect(sykdomsstegFerdigStatusSpy).toHaveBeenCalledTimes(1); + expect(navigerTilNesteStegSpy).toHaveBeenCalledWith(toOmsorgspersonerSteg, true); + }); + }); + + it('should get new sykdomsstatus after successfully posting vurdering, and when it responds with kanLøseAksjonspunkt=false, it should navigate user to whichever step needs work next', async () => { + renderVilkårsvurderingComponent(false, true); + await waitFor(async () => { + const textarea = screen.getByLabelText(/Gjør en vurdering av/i); + fireEvent.change(textarea, { target: { value: 'Foo Bar Baz' } }); + expect(textarea).toHaveValue('Foo Bar Baz'); + + const radio = screen.getByLabelText('Ja'); + fireEvent.click(radio); + }); + + const submitButton = screen.getByText('Bekreft'); + mockResolvedPostApiCall({ perioderMedEndringer: [] }); + fireEvent.click(submitButton); + + await waitFor(() => { + // one post with dryRun=true, another with dryRun=false + expect(httpPostSpy).toHaveBeenCalledTimes(2); + expect(sykdomsstegDokumentUferdigStatusSpy).toHaveBeenCalledTimes(1); + expect(navigerTilNesteStegSpy).toHaveBeenCalledWith(dokumentSteg, false); + }); + }); + + it('should get new sykdomsstatus after successfully posting vurdering, and if still not done with tilsyn & pleie, it should get an updated version of vurderingsoversikt data', async () => { + renderVilkårsvurderingComponent(false, false, true); + await waitFor(async () => { + const textarea = screen.getByLabelText(/Gjør en vurdering av/i); + fireEvent.change(textarea, { target: { value: 'Foo Bar Baz' } }); + expect(textarea).toHaveValue('Foo Bar Baz'); + + const radio = screen.getByLabelText('Ja'); + fireEvent.click(radio); + }); + + const submitButton = screen.getByText('Bekreft'); + mockResolvedPostApiCall({ perioderMedEndringer: [] }); + fireEvent.click(submitButton); + + // needed to clear call-count in mock before verifying that oppdaterVurderingsoversikt api-call is being done + httpGetSpy.mockClear(); + + await waitFor(() => { + // one post with dryRun=true, another with dryRun=false + expect(httpPostSpy).toHaveBeenCalledTimes(2); + expect(sykdomsstegKTPUferdigStatusSpy).toHaveBeenCalledTimes(1); + + expect(httpGetSpy).toHaveBeenCalledTimes(1); + expect(httpGetSpy).toHaveBeenCalledWith(vurderingsoversiktEndpoint, httpErrorHandlerMock, abortControllerMock); + }); + }); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/__tests__/reducer.spec.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/__tests__/reducer.spec.ts" new file mode 100644 index 0000000000..3b355415e2 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/__tests__/reducer.spec.ts" @@ -0,0 +1,109 @@ +import ActionType from '../actionTypes'; +import vilkårsvurderingReducer from '../reducer'; + +describe('vilkårsvurderingReducer', () => { + const vurderingsoversikt = {} as any; + + let state = {} as any; + + describe('when showing vurderingsoversikt', () => { + beforeAll(() => { + state = vilkårsvurderingReducer(state, { type: ActionType.VIS_VURDERINGSOVERSIKT, vurderingsoversikt }); + }); + + it('should put the specified new vurderingsoversikt on state', () => { + expect(state.vurderingsoversikt).toEqual(vurderingsoversikt); + }); + + it('should set isLoading to false', () => { + expect(state.isLoading).toBe(false); + }); + + it('should set visVurderingDetails to false', () => { + expect(state.visVurderingDetails).toBe(false); + }); + + it('should set skalViseRadForNyVurdering to false', () => { + expect(state.skalViseRadForNyVurdering).toBe(false); + }); + + it('should set visVurderingsoversiktFeilet to false', () => { + expect(state.vurderingsoversiktFeilet).toBe(false); + }); + }); + + describe('when vurderingsoversikt has failed', () => { + beforeAll(() => { + state = vilkårsvurderingReducer(state, { type: ActionType.VURDERINGSOVERSIKT_FEILET }); + }); + + it('should set vurderingsoversiktFeilet to true', () => { + expect(state.vurderingsoversiktFeilet).toBe(true); + }); + + it('should set isLoading to false', () => { + expect(state.isLoading).toBe(false); + }); + }); + + describe('when showing ny vurdering form', () => { + beforeAll(() => { + state = vilkårsvurderingReducer(state, { type: ActionType.VIS_NY_VURDERING_FORM }); + }); + + it('should reset any previously selected vurderingselement on state', () => { + expect(state.valgtVurderingselement).toBe(null); + }); + + it('should set isLoading to false', () => { + expect(state.isLoading).toBe(false); + }); + + it('should set skalViseRadForNyVurdering to true when there are no periods in resterendeVurderingsperioder', () => { + expect(state.skalViseRadForNyVurdering).toBe(true); + }); + + describe('when there are periods in resterendeVurderingsperioder', () => { + const anotherState = vilkårsvurderingReducer(state, { + type: ActionType.VIS_NY_VURDERING_FORM, + resterendeVurderingsperioder: [{} as any], + }); + + it('should set skalViseRadForNyVurdering to false', () => { + expect(anotherState.skalViseRadForNyVurdering).toBe(false); + }); + }); + }); + + describe('when pending', () => { + beforeAll(() => { + state = vilkårsvurderingReducer(state, { type: ActionType.PENDING }); + }); + + it('should set isLoading to true', () => { + expect(state.isLoading).toBe(true); + }); + + it('should set vurderingsoversiktFeilet to false', () => { + expect(state.vurderingsoversiktFeilet).toBe(false); + }); + }); + + describe('when cancelling in form', () => { + beforeAll(() => { + state = vilkårsvurderingReducer(state, { type: ActionType.AVBRYT_FORM }); + }); + + it('should set visVurderingDetails to false', () => { + expect(state.isLoading).toBe(true); + }); + + it('should reset any previously selected vurderingselement on state', () => { + expect(state.valgtVurderingselement).toBe(null); + }); + + it('should set skalViseRadForNyVurdering to false', () => { + expect(state.skalViseRadForNyVurdering).toBe(false); + }); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/actionTypes.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/actionTypes.ts" new file mode 100644 index 0000000000..3b84a5746b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/actionTypes.ts" @@ -0,0 +1,10 @@ +enum ActionType { + VIS_VURDERINGSOVERSIKT = 'visVurderingsoversikt', + VIS_NY_VURDERING_FORM = 'visNyVurderingForm', + VELG_VURDERINGSELEMENT = 'velgVurderingselement', + PENDING = 'pending', + VURDERINGSOVERSIKT_FEILET = 'vurderingsoversiktFeilet', + AVBRYT_FORM = 'avbrytForm', +} + +export default ActionType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/initialFormStateUtil.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/initialFormStateUtil.ts" new file mode 100644 index 0000000000..a0584262c4 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/initialFormStateUtil.ts" @@ -0,0 +1,66 @@ +import { Vurderingsversjon } from '../../../types/Vurdering'; +import { + FieldName as KTPFieldName, + VurderingAvTilsynsbehovFormState, +} from '../vurdering-av-tilsynsbehov-form/VurderingAvTilsynsbehovForm'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import Vurderingstype from '../../../types/Vurderingstype'; +import { + FieldName as TOFieldName, + VurderingAvToOmsorgspersonerFormState, +} from '../vurdering-av-to-omsorgspersoner-form/VurderingAvToOmsorgspersonerForm'; +import { + FieldName as LivetsSluttfaseFieldName, + VurderingAvLivetsSluttfaseFormState, +} from '../vurdering-av-livets-sluttfase-form/VurderingAvLivetsSluttfaseForm'; +import { + FieldName as LangvarigSykdomFieldName, + VurderingLangvarigSykdomFormState, +} from '../vurdering-av-langvarig-sykdom-form/VurderingLangvarigSykdomForm'; + +function buildInitialFormStateForEdit( + { tekst, resultat, perioder, dokumenter }: Vurderingsversjon, + vurderingstype: Vurderingstype, +): + | VurderingAvTilsynsbehovFormState + | VurderingAvToOmsorgspersonerFormState + | VurderingAvLivetsSluttfaseFormState + | VurderingLangvarigSykdomFormState { + const dokumenterFraVurdering = dokumenter.filter(dokument => dokument.benyttet).map(dokument => dokument.id); + + if (vurderingstype === Vurderingstype.KONTINUERLIG_TILSYN_OG_PLEIE) { + return { + [KTPFieldName.VURDERING_AV_KONTINUERLIG_TILSYN_OG_PLEIE]: tekst, + [KTPFieldName.HAR_BEHOV_FOR_KONTINUERLIG_TILSYN_OG_PLEIE]: resultat === Vurderingsresultat.OPPFYLT, + [KTPFieldName.PERIODER]: perioder, + [KTPFieldName.DOKUMENTER]: dokumenterFraVurdering, + }; + } + if (vurderingstype === Vurderingstype.TO_OMSORGSPERSONER) { + return { + [TOFieldName.VURDERING_AV_TO_OMSORGSPERSONER]: tekst, + [TOFieldName.HAR_BEHOV_FOR_TO_OMSORGSPERSONER]: resultat === Vurderingsresultat.OPPFYLT, + [TOFieldName.PERIODER]: perioder, + [TOFieldName.DOKUMENTER]: dokumenterFraVurdering, + }; + } + if (vurderingstype === Vurderingstype.LIVETS_SLUTTFASE) { + return { + [LivetsSluttfaseFieldName.VURDERING_AV_LIVETS_SLUTTFASE]: tekst, + [LivetsSluttfaseFieldName.ER_I_LIVETS_SLUTTFASE]: resultat, + [LivetsSluttfaseFieldName.DOKUMENTER]: dokumenterFraVurdering, + [LivetsSluttfaseFieldName.PERIODER]: perioder, + }; + } + if (vurderingstype === Vurderingstype.LANGVARIG_SYKDOM) { + return { + [LangvarigSykdomFieldName.VURDERING_LANGVARIG_SYKDOM]: tekst, + [LangvarigSykdomFieldName.HAR_LANGVARIG_SYKDOM]: resultat, + [LangvarigSykdomFieldName.DOKUMENTER]: dokumenterFraVurdering, + [LangvarigSykdomFieldName.PERIODER]: perioder, + }; + } + return undefined; +} + +export default buildInitialFormStateForEdit; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/reducer.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/reducer.ts" new file mode 100644 index 0000000000..a260350d3b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-tilsyn-og-pleie/reducer.ts" @@ -0,0 +1,72 @@ +import { Period } from '@fpsak-frontend/utils'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import ActionType from './actionTypes'; +import Vurderingselement from '../../../types/Vurderingselement'; + +interface State { + visVurderingDetails: boolean; + isLoading: boolean; + vurderingsoversikt: Vurderingsoversikt; + valgtVurderingselement: Vurderingselement; + skalViseRadForNyVurdering: boolean; + vurderingsoversiktFeilet: boolean; +} + +interface Action { + type: ActionType; + vurderingsoversikt?: Vurderingsoversikt; + valgtVurderingselement?: Vurderingselement; + resterendeVurderingsperioder?: Period[]; +} + +const vilkårsvurderingReducer = (state: State, action: Action): State => { + switch (action.type) { + case ActionType.VIS_VURDERINGSOVERSIKT: { + return { + ...state, + vurderingsoversikt: action.vurderingsoversikt, + isLoading: false, + visVurderingDetails: false, + skalViseRadForNyVurdering: false, + vurderingsoversiktFeilet: false, + }; + } + case ActionType.VURDERINGSOVERSIKT_FEILET: { + return { + ...state, + isLoading: false, + vurderingsoversiktFeilet: true, + }; + } + case ActionType.VIS_NY_VURDERING_FORM: + return { + ...state, + valgtVurderingselement: null, + visVurderingDetails: true, + skalViseRadForNyVurdering: !action.resterendeVurderingsperioder, + }; + case ActionType.VELG_VURDERINGSELEMENT: + return { + ...state, + valgtVurderingselement: action.valgtVurderingselement, + visVurderingDetails: true, + }; + case ActionType.PENDING: + return { + ...state, + isLoading: true, + vurderingsoversiktFeilet: false, + }; + case ActionType.AVBRYT_FORM: + return { + ...state, + visVurderingDetails: false, + valgtVurderingselement: null, + skalViseRadForNyVurdering: false, + }; + default: + return state; + } +}; + +export default vilkårsvurderingReducer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/Vilk\303\245rsvurderingAvToOmsorgspersoner.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/Vilk\303\245rsvurderingAvToOmsorgspersoner.tsx" new file mode 100644 index 0000000000..13b689397b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/Vilk\303\245rsvurderingAvToOmsorgspersoner.tsx" @@ -0,0 +1,183 @@ +import { get, Period } from '@fpsak-frontend/utils'; +import { NavigationWithDetailView, PageContainer, Box, Margin } from '@navikt/ft-plattform-komponenter'; +import React, { useMemo } from 'react'; +import Step, { StepId, toOmsorgspersonerSteg } from '../../../types/Step'; +import SykdomsstegStatusResponse from '../../../types/SykdomsstegStatusResponse'; +import Vurderingselement from '../../../types/Vurderingselement'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import { finnNesteStegForPleiepenger } from '../../../util/statusUtils'; +import ContainerContext from '../../context/ContainerContext'; +import Vurderingsnavigasjon from '../vurderingsnavigasjon/Vurderingsnavigasjon'; +import VurderingsoversiktMessages from '../vurderingsoversikt-messages/VurderingsoversiktMessages'; +import ActionType from './actionTypes'; +import vilkårsvurderingReducer from './reducer'; +import Vurderingsdetaljer from '../vurderingsdetaljer/Vurderingsdetaljer'; + +interface VilkårsvurderingAvToOmsorgspersonerProps { + navigerTilNesteSteg: (steg: Step) => void; + hentSykdomsstegStatus: () => Promise; + sykdomsstegStatus: SykdomsstegStatusResponse; +} + +const VilkårsvurderingAvToOmsorgspersoner = ({ + navigerTilNesteSteg, + hentSykdomsstegStatus, + sykdomsstegStatus, +}: VilkårsvurderingAvToOmsorgspersonerProps): JSX.Element => { + const { endpoints, onFinished, httpErrorHandler, readOnly } = React.useContext(ContainerContext); + const controller = useMemo(() => new AbortController(), []); + + const [state, dispatch] = React.useReducer(vilkårsvurderingReducer, { + visVurderingDetails: false, + isLoading: true, + vurderingsoversikt: null, + valgtVurderingselement: null, + vurderingsoversiktFeilet: false, + skalViseRadForNyVurdering: false, + }); + + const { + vurderingsoversikt, + isLoading, + visVurderingDetails, + valgtVurderingselement, + vurderingsoversiktFeilet, + skalViseRadForNyVurdering, + } = state; + + const { manglerGodkjentLegeerklæring } = sykdomsstegStatus; + const harGyldigSignatur = !manglerGodkjentLegeerklæring; + + const getVurderingsoversikt = () => + get(endpoints.vurderingsoversiktBehovForToOmsorgspersoner, httpErrorHandler, { + signal: controller.signal, + }); + + const visVurderingsoversikt = (nyVurderingsoversikt: Vurderingsoversikt) => { + dispatch({ type: ActionType.VIS_VURDERINGSOVERSIKT, vurderingsoversikt: nyVurderingsoversikt }); + }; + + const handleError = () => { + dispatch({ type: ActionType.VURDERINGSOVERSIKT_FEILET }); + }; + + const visNyVurderingForm = (resterendeVurderingsperioder?: Period[]) => { + dispatch({ type: ActionType.VIS_NY_VURDERING_FORM, resterendeVurderingsperioder }); + }; + + const onAvbryt = () => { + dispatch({ + type: ActionType.AVBRYT_FORM, + }); + }; + + const åpneFørstePeriodeSomMåBehandles = (nyVurderingsoversikt: Vurderingsoversikt) => { + const harEnPeriodeSomMåBehandles = nyVurderingsoversikt?.resterendeVurderingsperioder?.length > 0; + if (harEnPeriodeSomMåBehandles) { + visNyVurderingForm(nyVurderingsoversikt.resterendeVurderingsperioder); + } + }; + + React.useEffect(() => { + let isMounted = true; + getVurderingsoversikt() + .then(vurderingsoversiktData => { + if (isMounted) { + const nyVurderingsoversikt = new Vurderingsoversikt(vurderingsoversiktData); + visVurderingsoversikt(nyVurderingsoversikt); + åpneFørstePeriodeSomMåBehandles(nyVurderingsoversikt); + } + }) + .catch(handleError); + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + const velgVurderingselement = (nyvalgtVurderingselement: Vurderingselement) => { + dispatch({ type: ActionType.VELG_VURDERINGSELEMENT, valgtVurderingselement: nyvalgtVurderingselement }); + }; + + const oppdaterVurderingsoversikt = () => { + dispatch({ type: ActionType.PENDING }); + getVurderingsoversikt().then(vurderingsoversiktData => { + const nyVurderingsoversikt = new Vurderingsoversikt(vurderingsoversiktData); + visVurderingsoversikt(nyVurderingsoversikt); + }); + }; + + const onVurderingLagret = () => { + dispatch({ type: ActionType.PENDING }); + hentSykdomsstegStatus().then(status => { + if (status.kanLøseAksjonspunkt) { + onFinished(); + return; + } + + const nesteSteg = finnNesteStegForPleiepenger(status); + if (nesteSteg === toOmsorgspersonerSteg || nesteSteg === null) { + oppdaterVurderingsoversikt(); + } else if (nesteSteg !== null) { + navigerTilNesteSteg(nesteSteg); + } + }); + }; + + const setMargin = () => { + if (vurderingsoversikt.harPerioderSomSkalVurderes() || !harGyldigSignatur) { + return Margin.medium; + } + return null; + }; + + const skalViseValgfriePerioder = !readOnly && vurderingsoversikt?.resterendeValgfrieVurderingsperioder.length > 0; + + const skalViseOpprettVurderingKnapp = + !vurderingsoversikt?.harPerioderSomSkalVurderes() && !skalViseRadForNyVurdering && harGyldigSignatur; + + const skalViseNyVurderingForm = visVurderingDetails && !valgtVurderingselement; + + return ( + + + {vurderingsoversikt?.harPerioderÅVise() && ( + + { + if (vurderingsoversikt.harPerioderÅVise()) { + return ( + + ); + } + return null; + }} + showDetailSection={visVurderingDetails} + detailSection={() => ( + + )} + /> + + )} + + ); +}; + +export default VilkårsvurderingAvToOmsorgspersoner; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/__tests__/Vilk\303\245rsvurderingAvToOmsorgspersoner.spec.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/__tests__/Vilk\303\245rsvurderingAvToOmsorgspersoner.spec.tsx" new file mode 100644 index 0000000000..beb57b4d83 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/__tests__/Vilk\303\245rsvurderingAvToOmsorgspersoner.spec.tsx" @@ -0,0 +1,325 @@ +import React from 'react'; +import { httpUtils } from '@fpsak-frontend/utils'; +import { render, fireEvent, waitFor, screen } from '@testing-library/react'; +import VilkårsvurderingAvToOmsorgspersoner from '../VilkårsvurderingAvToOmsorgspersoner'; +import ContainerContext from '../../../context/ContainerContext'; +import VurderingContext from '../../../context/VurderingContext'; +import Vurderingstype from '../../../../types/Vurderingstype'; +import { dokumentSteg } from '../../../../types/Step'; + +const vurderingsoversiktEndpoint = 'vurderingsoversikt-mock'; +const vurderingsopprettelseEndpoint = 'vurderingsopprettelse-mock'; + +const httpErrorHandlerMock = () => null; +const abortControllerMock = { signal: new AbortController().signal }; + +const vurderingsoversiktMock = { + perioderSomKanVurderes: [], + resterendeVurderingsperioder: [], + resterendeValgfrieVurderingsperioder: [], + søknadsperioderTilBehandling: [], + vurderingselementer: [], + links: [ + { + rel: 'sykdom-vurdering-opprettelse', + href: vurderingsopprettelseEndpoint, + requestPayload: { + behandlingUuid: 'foo', + }, + }, + ], +} as any; + +window.scroll = () => null; + +const onFinishedMock = { + fn: () => null, +}; + +const contextWrapper = ui => + render( + + + {ui} + + , + ); + +const navigerTilNesteStegMock = { + fn: () => null, +}; + +const getFunctionThatReturnsAResolvedPromise = data => () => + new Promise(resolve => { + resolve(data); + }); + +const sykdomsstegFerdigStatusMock = { + fn: getFunctionThatReturnsAResolvedPromise({ kanLøseAksjonspunkt: true }), +}; + +const sykdomsstegDokumentUferdigStatusMock = { + fn: getFunctionThatReturnsAResolvedPromise({ kanLøseAksjonspunkt: false, harUklassifiserteDokumenter: true }), +}; + +const sykdomsstegToOmsorgspersonerUferdigStatusMock = { + fn: getFunctionThatReturnsAResolvedPromise({ + kanLøseAksjonspunkt: false, + harUklassifiserteDokumenter: false, + manglerVurderingAvToOmsorgspersoner: true, + }), +}; + +const renderVilkårsvurderingComponent = ( + kanLøseAksjonspunkt?: boolean, + harUklassifiserteDokumenter?: boolean, + manglerToOmsorgspersonerVurdering?: boolean, +) => { + let hentSykdomsstegStatusMock = sykdomsstegFerdigStatusMock; + if (kanLøseAksjonspunkt) { + hentSykdomsstegStatusMock = sykdomsstegFerdigStatusMock; + } + if (harUklassifiserteDokumenter) { + hentSykdomsstegStatusMock = sykdomsstegDokumentUferdigStatusMock; + } + if (manglerToOmsorgspersonerVurdering) { + hentSykdomsstegStatusMock = sykdomsstegToOmsorgspersonerUferdigStatusMock; + } + + return contextWrapper( + , + ); +}; + +describe('VilkårsvurderingAvToOmsorgspersoner', () => { + let httpGetSpy = null; + let httpPostSpy = null; + + let sykdomsstegFerdigStatusSpy = null; + let sykdomsstegDokumentUferdigStatusSpy = null; + let sykdomsstegToOmsorgspersonerUferdigStatusSpy = null; + + let navigerTilNesteStegSpy = null; + let onFinishedSpy = null; + + beforeAll(() => { + httpGetSpy = jest.spyOn(httpUtils, 'get'); + httpPostSpy = jest.spyOn(httpUtils, 'post'); + + sykdomsstegFerdigStatusSpy = jest.spyOn(sykdomsstegFerdigStatusMock, 'fn'); + sykdomsstegDokumentUferdigStatusSpy = jest.spyOn(sykdomsstegDokumentUferdigStatusMock, 'fn'); + sykdomsstegToOmsorgspersonerUferdigStatusSpy = jest.spyOn(sykdomsstegToOmsorgspersonerUferdigStatusMock, 'fn'); + + navigerTilNesteStegSpy = jest.spyOn(navigerTilNesteStegMock, 'fn'); + onFinishedSpy = jest.spyOn(onFinishedMock, 'fn'); + }); + + const mockResolvedGetApiCallOnce = data => { + httpGetSpy.mockImplementationOnce( + () => + new Promise(resolve => { + resolve(data); + }), + ); + }; + + const mockRejectedGetApiCallOnce = () => { + httpGetSpy.mockImplementationOnce( + () => + new Promise((resolve, reject) => { + reject(); + }), + ); + }; + + const mockResolvedGetApiCall = data => { + httpGetSpy.mockImplementation( + () => + new Promise(resolve => { + resolve(data); + }), + ); + }; + + const mockResolvedPostApiCall = data => { + httpPostSpy.mockImplementation( + () => + new Promise(resolve => { + resolve(data); + }), + ); + }; + + describe('when vurderingsoversikt-http call is successful and data is in the expected format', () => { + it('should render vurderingsoversikt presentation properly during and after the data has been fetched', async () => { + mockResolvedGetApiCallOnce(vurderingsoversiktMock); + const { getByText } = renderVilkårsvurderingComponent(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + await waitFor(() => expect(getByText(/Ingen perioder å vurdere/i)).toBeInTheDocument()); + }); + }); + + describe('when vurderingsoversikt-http call is successful, but data is not in the expected format', () => { + it('should render vurderingsoversikt presentation properly after error handling', async () => { + mockResolvedGetApiCallOnce({}); + const { getByText } = renderVilkårsvurderingComponent(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + await waitFor(() => expect(getByText(/Noe gikk galt, vennligst prøv igjen senere/i)).toBeInTheDocument()); + }); + }); + + describe('when vurderingsoversikt-http call fails', () => { + it('should render vurderingsoversikt properly after error handling', async () => { + mockRejectedGetApiCallOnce(); + const { getByText } = renderVilkårsvurderingComponent(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + await waitFor(() => expect(getByText(/Noe gikk galt, vennligst prøv igjen senere/i)).toBeInTheDocument()); + }); + }); + + describe('when there are periods that need to be considered', () => { + beforeEach(() => { + mockResolvedGetApiCallOnce({ + ...vurderingsoversiktMock, + resterendeVurderingsperioder: [{ fom: '2028-01-01', tom: '2028-01-01' }], + }); + }); + + it('should open form for periods that need consideration by default', async () => { + renderVilkårsvurderingComponent(); + await waitFor(() => { + expect(screen.getByText(/Perioden må vurderes/i)).toBeInTheDocument(); + expect( + screen.getByText(/Vurder behov for to omsorgspersoner for 01.01.2028 - 01.01.2028/i), + ).toBeInTheDocument(); + expect(screen.queryByText('Ny vurdering')).toBeNull(); + }); + }); + }); + + describe('when there are no periods that need to be considered', () => { + beforeEach(() => { + mockResolvedGetApiCallOnce({ + ...vurderingsoversiktMock, + vurderingselementer: [{ periode: { fom: '2028-01-01', tom: '2028-01-01' } }], + }); + }); + + it('should open vurdering-form when Ny vurdering-button is clicked, and form should be closeable by clicking Avbryt button', async () => { + const { getByText, queryByText } = renderVilkårsvurderingComponent(); + await waitFor(() => { + expect(queryByText(/Vurdering av to omsorgspersoner/)).toBeNull(); + fireEvent.click(screen.getByText('Ny vurdering')); + expect(getByText(/Vurdering av to omsorgspersoner/i)).toBeInTheDocument(); + fireEvent.click(screen.getByText(/Avbryt/i)); + expect(queryByText(/Vurdering av to omsorgspersoner/)).toBeNull(); + }); + }); + }); + + describe('when vurdering form is submitted and it has been saved successfully', () => { + beforeEach(() => { + mockResolvedGetApiCall({ + ...vurderingsoversiktMock, + resterendeVurderingsperioder: [{ fom: '2028-01-01', tom: '2028-01-01' }], + perioderSomKanVurderes: [{ fom: '2028-01-01', tom: '2028-01-01' }], + }); + }); + + afterEach(() => { + httpGetSpy.mockClear(); + httpPostSpy.mockClear(); + navigerTilNesteStegSpy.mockClear(); + }); + + // eslint-disable-next-line max-len + it('should get new sykdomsstatus after successfully posting vurdering, and when it responds with kanLøseAksjonspunkt=true, it call onFinished()', async () => { + renderVilkårsvurderingComponent(true); + await waitFor(async () => { + const textarea = screen.getByLabelText(/Gjør en vurdering av/i); + fireEvent.change(textarea, { target: { value: 'Foo Bar Baz' } }); + expect(textarea).toHaveValue('Foo Bar Baz'); + + const radio = screen.getByLabelText('Ja'); + fireEvent.click(radio); + }); + + const submitButton = screen.getByText('Bekreft'); + mockResolvedPostApiCall({ perioderMedEndringer: [] }); + fireEvent.click(submitButton); + + await waitFor(() => { + // one post with dryRun=true, another with dryRun=false + expect(httpPostSpy).toHaveBeenCalledTimes(2); + expect(sykdomsstegFerdigStatusSpy).toHaveBeenCalledTimes(1); + expect(onFinishedSpy).toHaveBeenCalledTimes(1); + }); + }); + + // eslint-disable-next-line max-len + it('should get new sykdomsstatus after successfully posting vurdering, and when it responds with kanLøseAksjonspunkt=false, it should navigate user to whichever step needs work next', async () => { + renderVilkårsvurderingComponent(false, true); + await waitFor(async () => { + const textarea = screen.getByLabelText(/Gjør en vurdering av/i); + fireEvent.change(textarea, { target: { value: 'Foo Bar Baz' } }); + expect(textarea).toHaveValue('Foo Bar Baz'); + + const radio = screen.getByLabelText('Ja'); + fireEvent.click(radio); + }); + + const submitButton = screen.getByText('Bekreft'); + mockResolvedPostApiCall({ perioderMedEndringer: [] }); + fireEvent.click(submitButton); + + await waitFor(() => { + // one post with dryRun=true, another with dryRun=false + expect(httpPostSpy).toHaveBeenCalledTimes(2); + expect(sykdomsstegDokumentUferdigStatusSpy).toHaveBeenCalledTimes(1); + expect(navigerTilNesteStegSpy).toHaveBeenCalledWith(dokumentSteg); + }); + }); + + // eslint-disable-next-line max-len + it('should get new sykdomsstatus after successfully posting vurdering, and if still not done with to omsorgspersoner, it should get an updated version of vurderingsoversikt data', async () => { + renderVilkårsvurderingComponent(false, false, true); + await waitFor(async () => { + const textarea = screen.getByLabelText(/Gjør en vurdering av/i); + fireEvent.change(textarea, { target: { value: 'Foo Bar Baz' } }); + expect(textarea).toHaveValue('Foo Bar Baz'); + + const radio = screen.getByLabelText('Ja'); + fireEvent.click(radio); + }); + + const submitButton = screen.getByText('Bekreft'); + mockResolvedPostApiCall({ perioderMedEndringer: [] }); + fireEvent.click(submitButton); + + // needed to clear call-count in mock before verifying that oppdaterVurderingsoversikt api-call is being done + httpGetSpy.mockClear(); + + await waitFor(() => { + // one post with dryRun=true, another with dryRun=false + expect(httpPostSpy).toHaveBeenCalledTimes(2); + expect(sykdomsstegToOmsorgspersonerUferdigStatusSpy).toHaveBeenCalledTimes(1); + + expect(httpGetSpy).toHaveBeenCalledTimes(1); + expect(httpGetSpy).toHaveBeenCalledWith(vurderingsoversiktEndpoint, httpErrorHandlerMock, abortControllerMock); + }); + }); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/__tests__/reducer.spec.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/__tests__/reducer.spec.ts" new file mode 100644 index 0000000000..3b355415e2 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/__tests__/reducer.spec.ts" @@ -0,0 +1,109 @@ +import ActionType from '../actionTypes'; +import vilkårsvurderingReducer from '../reducer'; + +describe('vilkårsvurderingReducer', () => { + const vurderingsoversikt = {} as any; + + let state = {} as any; + + describe('when showing vurderingsoversikt', () => { + beforeAll(() => { + state = vilkårsvurderingReducer(state, { type: ActionType.VIS_VURDERINGSOVERSIKT, vurderingsoversikt }); + }); + + it('should put the specified new vurderingsoversikt on state', () => { + expect(state.vurderingsoversikt).toEqual(vurderingsoversikt); + }); + + it('should set isLoading to false', () => { + expect(state.isLoading).toBe(false); + }); + + it('should set visVurderingDetails to false', () => { + expect(state.visVurderingDetails).toBe(false); + }); + + it('should set skalViseRadForNyVurdering to false', () => { + expect(state.skalViseRadForNyVurdering).toBe(false); + }); + + it('should set visVurderingsoversiktFeilet to false', () => { + expect(state.vurderingsoversiktFeilet).toBe(false); + }); + }); + + describe('when vurderingsoversikt has failed', () => { + beforeAll(() => { + state = vilkårsvurderingReducer(state, { type: ActionType.VURDERINGSOVERSIKT_FEILET }); + }); + + it('should set vurderingsoversiktFeilet to true', () => { + expect(state.vurderingsoversiktFeilet).toBe(true); + }); + + it('should set isLoading to false', () => { + expect(state.isLoading).toBe(false); + }); + }); + + describe('when showing ny vurdering form', () => { + beforeAll(() => { + state = vilkårsvurderingReducer(state, { type: ActionType.VIS_NY_VURDERING_FORM }); + }); + + it('should reset any previously selected vurderingselement on state', () => { + expect(state.valgtVurderingselement).toBe(null); + }); + + it('should set isLoading to false', () => { + expect(state.isLoading).toBe(false); + }); + + it('should set skalViseRadForNyVurdering to true when there are no periods in resterendeVurderingsperioder', () => { + expect(state.skalViseRadForNyVurdering).toBe(true); + }); + + describe('when there are periods in resterendeVurderingsperioder', () => { + const anotherState = vilkårsvurderingReducer(state, { + type: ActionType.VIS_NY_VURDERING_FORM, + resterendeVurderingsperioder: [{} as any], + }); + + it('should set skalViseRadForNyVurdering to false', () => { + expect(anotherState.skalViseRadForNyVurdering).toBe(false); + }); + }); + }); + + describe('when pending', () => { + beforeAll(() => { + state = vilkårsvurderingReducer(state, { type: ActionType.PENDING }); + }); + + it('should set isLoading to true', () => { + expect(state.isLoading).toBe(true); + }); + + it('should set vurderingsoversiktFeilet to false', () => { + expect(state.vurderingsoversiktFeilet).toBe(false); + }); + }); + + describe('when cancelling in form', () => { + beforeAll(() => { + state = vilkårsvurderingReducer(state, { type: ActionType.AVBRYT_FORM }); + }); + + it('should set visVurderingDetails to false', () => { + expect(state.isLoading).toBe(true); + }); + + it('should reset any previously selected vurderingselement on state', () => { + expect(state.valgtVurderingselement).toBe(null); + }); + + it('should set skalViseRadForNyVurdering to false', () => { + expect(state.skalViseRadForNyVurdering).toBe(false); + }); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/actionTypes.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/actionTypes.ts" new file mode 100644 index 0000000000..3b84a5746b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/actionTypes.ts" @@ -0,0 +1,10 @@ +enum ActionType { + VIS_VURDERINGSOVERSIKT = 'visVurderingsoversikt', + VIS_NY_VURDERING_FORM = 'visNyVurderingForm', + VELG_VURDERINGSELEMENT = 'velgVurderingselement', + PENDING = 'pending', + VURDERINGSOVERSIKT_FEILET = 'vurderingsoversiktFeilet', + AVBRYT_FORM = 'avbrytForm', +} + +export default ActionType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/reducer.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/reducer.ts" new file mode 100644 index 0000000000..a260350d3b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-av-to-omsorgspersoner/reducer.ts" @@ -0,0 +1,72 @@ +import { Period } from '@fpsak-frontend/utils'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import ActionType from './actionTypes'; +import Vurderingselement from '../../../types/Vurderingselement'; + +interface State { + visVurderingDetails: boolean; + isLoading: boolean; + vurderingsoversikt: Vurderingsoversikt; + valgtVurderingselement: Vurderingselement; + skalViseRadForNyVurdering: boolean; + vurderingsoversiktFeilet: boolean; +} + +interface Action { + type: ActionType; + vurderingsoversikt?: Vurderingsoversikt; + valgtVurderingselement?: Vurderingselement; + resterendeVurderingsperioder?: Period[]; +} + +const vilkårsvurderingReducer = (state: State, action: Action): State => { + switch (action.type) { + case ActionType.VIS_VURDERINGSOVERSIKT: { + return { + ...state, + vurderingsoversikt: action.vurderingsoversikt, + isLoading: false, + visVurderingDetails: false, + skalViseRadForNyVurdering: false, + vurderingsoversiktFeilet: false, + }; + } + case ActionType.VURDERINGSOVERSIKT_FEILET: { + return { + ...state, + isLoading: false, + vurderingsoversiktFeilet: true, + }; + } + case ActionType.VIS_NY_VURDERING_FORM: + return { + ...state, + valgtVurderingselement: null, + visVurderingDetails: true, + skalViseRadForNyVurdering: !action.resterendeVurderingsperioder, + }; + case ActionType.VELG_VURDERINGSELEMENT: + return { + ...state, + valgtVurderingselement: action.valgtVurderingselement, + visVurderingDetails: true, + }; + case ActionType.PENDING: + return { + ...state, + isLoading: true, + vurderingsoversiktFeilet: false, + }; + case ActionType.AVBRYT_FORM: + return { + ...state, + visVurderingDetails: false, + valgtVurderingselement: null, + skalViseRadForNyVurdering: false, + }; + default: + return state; + } +}; + +export default vilkårsvurderingReducer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-langvarig-sykdom/VilkarsvurderingLangvarigSykdom.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-langvarig-sykdom/VilkarsvurderingLangvarigSykdom.tsx" new file mode 100644 index 0000000000..92ac5cee70 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-langvarig-sykdom/VilkarsvurderingLangvarigSykdom.tsx" @@ -0,0 +1,186 @@ +import { get, Period } from '@fpsak-frontend/utils'; +import { Box, Margin, NavigationWithDetailView, PageContainer } from '@navikt/ft-plattform-komponenter'; +import React, { useMemo } from 'react'; +import Step, { langvarigSykdomSteg, StepId } from '../../../types/Step'; +import SykdomsstegStatusResponse from '../../../types/SykdomsstegStatusResponse'; +import Vurderingselement from '../../../types/Vurderingselement'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import { finnNesteStegForOpplæringspenger } from '../../../util/statusUtils'; +import ContainerContext from '../../context/ContainerContext'; +import Vurderingsnavigasjon from '../vurderingsnavigasjon/Vurderingsnavigasjon'; +import ActionType from './actionTypes'; +import vilkårsvurderingReducer from './reducer'; +import Vurderingsdetaljer from '../vurderingsdetaljer/Vurderingsdetaljer'; + +import BehandlingType from '../../../constants/BehandlingType'; +import FagsakYtelseType from '../../../constants/FagsakYtelseType'; +import VurderingsoversiktLangvarigSykdomMessages from '../vurderingsoversikt-langvarig-sykdom-messages/VurderingsoversiktLangvarigSykdomMessages'; + +interface VilkårsvurderingLangvarigSykdomProps { + navigerTilNesteSteg: (steg: Step, ikkeMarkerSteg?: boolean) => void; + hentSykdomsstegStatus: () => Promise; + sykdomsstegStatus: SykdomsstegStatusResponse; +} + +const VilkårsvurderingLangvarigSykdom = ({ + navigerTilNesteSteg, + hentSykdomsstegStatus, + sykdomsstegStatus, +}: VilkårsvurderingLangvarigSykdomProps): JSX.Element => { + const { endpoints, httpErrorHandler, fagsakYtelseType, behandlingType } = React.useContext(ContainerContext); + const controller = useMemo(() => new AbortController(), []); + + const [state, dispatch] = React.useReducer(vilkårsvurderingReducer, { + visVurderingDetails: false, + isLoading: true, + vurderingsoversikt: null, + valgtVurderingselement: null, + skalViseRadForNyVurdering: false, + vurderingsoversiktFeilet: false, + }); + + const { + vurderingsoversikt, + isLoading, + visVurderingDetails, + valgtVurderingselement, + skalViseRadForNyVurdering, + vurderingsoversiktFeilet, + } = state; + + const { manglerGodkjentLegeerklæring } = sykdomsstegStatus; + const harGyldigSignatur = !manglerGodkjentLegeerklæring; + + const getVurderingsoversikt = () => + get(endpoints.vurderingsoversiktLangvarigSykdom, httpErrorHandler, { + signal: controller.signal, + }); + + const visVurderingsoversikt = (nyVurderingsoversikt: Vurderingsoversikt) => { + dispatch({ type: ActionType.VIS_VURDERINGSOVERSIKT, vurderingsoversikt: nyVurderingsoversikt }); + }; + + const handleError = () => { + dispatch({ type: ActionType.VURDERINGSOVERSIKT_FEILET }); + }; + + const visNyVurderingForm = (resterendeVurderingsperioder?: Period[]) => { + dispatch({ + type: ActionType.VIS_NY_VURDERING_FORM, + resterendeVurderingsperioder, + }); + }; + + const åpneFørstePeriodeSomMåBehandles = (nyVurderingsoversikt: Vurderingsoversikt) => { + const harEnPeriodeSomMåBehandles = nyVurderingsoversikt?.resterendeVurderingsperioder?.length > 0; + if (harEnPeriodeSomMåBehandles) { + visNyVurderingForm(nyVurderingsoversikt.resterendeVurderingsperioder); + } + }; + + React.useEffect(() => { + let isMounted = true; + getVurderingsoversikt() + .then(vurderingsoversiktData => { + if (isMounted) { + const nyVurderingsoversikt = new Vurderingsoversikt(vurderingsoversiktData); + visVurderingsoversikt(nyVurderingsoversikt); + åpneFørstePeriodeSomMåBehandles(nyVurderingsoversikt); + } + }) + .catch(handleError); + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + const velgVurderingselement = (nyValgtVurderingselement: Vurderingselement) => { + dispatch({ type: ActionType.VELG_VURDERINGSELEMENT, valgtVurderingselement: nyValgtVurderingselement }); + }; + + const oppdaterVurderingsoversikt = () => { + dispatch({ type: ActionType.PENDING }); + getVurderingsoversikt().then(vurderingsoversiktData => { + const nyVurderingsoversikt = new Vurderingsoversikt(vurderingsoversiktData); + visVurderingsoversikt(nyVurderingsoversikt); + }); + }; + + const onAvbryt = () => { + dispatch({ + type: ActionType.AVBRYT_FORM, + }); + }; + + const onVurderingLagret = () => { + dispatch({ type: ActionType.PENDING }); + hentSykdomsstegStatus() + .then(status => { + const nesteSteg = finnNesteStegForOpplæringspenger(status); + if (nesteSteg === langvarigSykdomSteg || nesteSteg === null) { + oppdaterVurderingsoversikt(); + } else if (nesteSteg !== null) { + navigerTilNesteSteg(nesteSteg); + } + }) + .catch(handleError); + }; + + const setMargin = () => { + if (vurderingsoversikt.harPerioderSomSkalVurderes() || !harGyldigSignatur) { + return Margin.medium; + } + return null; + }; + + const skalViseOpprettVurderingKnapp = () => { + if (fagsakYtelseType === FagsakYtelseType.OPPLÆRINGSPENGER && BehandlingType.FORSTEGANGSSOKNAD === behandlingType) + return false; + + return !vurderingsoversikt?.harPerioderSomSkalVurderes() && + !skalViseRadForNyVurdering && + harGyldigSignatur && + fagsakYtelseType === FagsakYtelseType.OPPLÆRINGSPENGER + ? behandlingType !== BehandlingType.FORSTEGANGSSOKNAD + : true; + }; + + const skalViseNyVurderingForm = visVurderingDetails && !valgtVurderingselement; + + return ( + + + {vurderingsoversikt?.harPerioderÅVise() && ( + + ( + + )} + showDetailSection={visVurderingDetails} + detailSection={() => ( + + )} + /> + + )} + + ); +}; + +export default VilkårsvurderingLangvarigSykdom; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-langvarig-sykdom/actionTypes.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-langvarig-sykdom/actionTypes.ts" new file mode 100644 index 0000000000..3b84a5746b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-langvarig-sykdom/actionTypes.ts" @@ -0,0 +1,10 @@ +enum ActionType { + VIS_VURDERINGSOVERSIKT = 'visVurderingsoversikt', + VIS_NY_VURDERING_FORM = 'visNyVurderingForm', + VELG_VURDERINGSELEMENT = 'velgVurderingselement', + PENDING = 'pending', + VURDERINGSOVERSIKT_FEILET = 'vurderingsoversiktFeilet', + AVBRYT_FORM = 'avbrytForm', +} + +export default ActionType; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-langvarig-sykdom/reducer.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-langvarig-sykdom/reducer.ts" new file mode 100644 index 0000000000..a260350d3b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vilk\303\245rsvurdering-langvarig-sykdom/reducer.ts" @@ -0,0 +1,72 @@ +import { Period } from '@fpsak-frontend/utils'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import ActionType from './actionTypes'; +import Vurderingselement from '../../../types/Vurderingselement'; + +interface State { + visVurderingDetails: boolean; + isLoading: boolean; + vurderingsoversikt: Vurderingsoversikt; + valgtVurderingselement: Vurderingselement; + skalViseRadForNyVurdering: boolean; + vurderingsoversiktFeilet: boolean; +} + +interface Action { + type: ActionType; + vurderingsoversikt?: Vurderingsoversikt; + valgtVurderingselement?: Vurderingselement; + resterendeVurderingsperioder?: Period[]; +} + +const vilkårsvurderingReducer = (state: State, action: Action): State => { + switch (action.type) { + case ActionType.VIS_VURDERINGSOVERSIKT: { + return { + ...state, + vurderingsoversikt: action.vurderingsoversikt, + isLoading: false, + visVurderingDetails: false, + skalViseRadForNyVurdering: false, + vurderingsoversiktFeilet: false, + }; + } + case ActionType.VURDERINGSOVERSIKT_FEILET: { + return { + ...state, + isLoading: false, + vurderingsoversiktFeilet: true, + }; + } + case ActionType.VIS_NY_VURDERING_FORM: + return { + ...state, + valgtVurderingselement: null, + visVurderingDetails: true, + skalViseRadForNyVurdering: !action.resterendeVurderingsperioder, + }; + case ActionType.VELG_VURDERINGSELEMENT: + return { + ...state, + valgtVurderingselement: action.valgtVurderingselement, + visVurderingDetails: true, + }; + case ActionType.PENDING: + return { + ...state, + isLoading: true, + vurderingsoversiktFeilet: false, + }; + case ActionType.AVBRYT_FORM: + return { + ...state, + visVurderingDetails: false, + valgtVurderingselement: null, + skalViseRadForNyVurdering: false, + }; + default: + return state; + } +}; + +export default vilkårsvurderingReducer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurder-behov-for-perioder-melding/VurderBehovForPerioderMelding.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurder-behov-for-perioder-melding/VurderBehovForPerioderMelding.tsx" new file mode 100644 index 0000000000..2c378e2705 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurder-behov-for-perioder-melding/VurderBehovForPerioderMelding.tsx" @@ -0,0 +1,21 @@ +import React from 'react'; +import { Period, getHumanReadablePeriodString } from '@fpsak-frontend/utils'; +import { Alert } from '@navikt/ds-react'; + +interface VurderBehovForPerioderMeldingProps { + vurderingsnavn: string; + perioder: Period[]; +} + +const VurderBehovForPerioderMelding = ({ + vurderingsnavn, + perioder, +}: VurderBehovForPerioderMeldingProps): JSX.Element => ( + + Vurder behov for + {vurderingsnavn} + for ${getHumanReadablePeriodString(perioder)} + +); + +export default VurderBehovForPerioderMelding; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-form/StjerneIkon.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-form/StjerneIkon.tsx" new file mode 100644 index 0000000000..592034fee6 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-form/StjerneIkon.tsx" @@ -0,0 +1,13 @@ +import * as React from 'react'; + +const StjerneIkon: React.FC = () => ( + + + +); + +export default StjerneIkon; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-form/vurderingForm.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-form/vurderingForm.css" new file mode 100644 index 0000000000..d4bef72638 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-form/vurderingForm.css" @@ -0,0 +1,70 @@ +.begrunnelsesfelt textarea { + max-height: 500px; + overflow-y: auto; +} + +.begrunnelsesfelt__labeltekst { + font-weight: 400; + margin-bottom: 0; +} + +.begrunnelsesfelt__liste { + margin-top: 0; + padding-left: 1rem; + font-weight: 400; +} + +.visDokumenterKnapp { + margin-top: 0.75rem; + color: #0067c5; + background: none; + text-decoration: underline; + cursor: pointer; + border: none; + padding: 0; +} + +.checkboxGroupWrapper legend { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} + +.fjernFilterKnapp { + align-items: center; + background: #c9c9c9; + border: none; + cursor: pointer; + display: flex; + margin-right: 0.5rem; + padding: 0.3125rem 0.5625rem; +} + +.fjernFilterKnapp svg { + margin-left: 0.5rem; + height: 0.75rem; + width: 0.75rem; +} + +.filterKnappContainer { + display: flex; + margin: 1.25rem 0 1.5rem; +} + +.filterContainer { + position: relative; + margin-top: 0.75rem; +} + +.dokumentEtiketter { + display: flex; +} + +.dokumentEtiketter :global .contentWithTooltip { + margin-right: 0.5rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-langvarig-sykdom-form/VurderingLangvarigSykdomForm.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-langvarig-sykdom-form/VurderingLangvarigSykdomForm.tsx" new file mode 100644 index 0000000000..48f1352365 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-langvarig-sykdom-form/VurderingLangvarigSykdomForm.tsx" @@ -0,0 +1,371 @@ +import { Close } from '@navikt/ds-icons'; +import { Alert, Label, Link } from '@navikt/ds-react'; +import { Box, ContentWithTooltip, Form, Margin, OnePersonOutlineGray } from '@navikt/ft-plattform-komponenter'; +import { CheckboxGroupRHF, PeriodpickerListRHF, RadioGroupPanelRHF, TextAreaRHF } from '@fpsak-frontend/form'; +import { Period } from '@fpsak-frontend/utils'; +import React, { useState } from 'react'; +import { FormProvider, useForm, useWatch } from 'react-hook-form'; +import Dokument from '../../../types/Dokument'; +import { Vurderingsversjon } from '../../../types/Vurdering'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import { + finnHullIPerioder, + finnMaksavgrensningerForPerioder, + slåSammenSammenhengendePerioder, +} from '../../../util/periodUtils'; +import ContainerContext from '../../context/ContainerContext'; +import { fomDatoErFørTomDato, harBruktDokumentasjon, required } from '../../form/validators'; +import AddButton from '../add-button/AddButton'; +import DeleteButton from '../delete-button/DeleteButton'; +import DetailViewVurdering from '../detail-view-vurdering/DetailViewVurdering'; +import DokumentLink from '../dokument-link/DokumentLink'; +import StjerneIkon from '../vurdering-av-form/StjerneIkon'; +import styles from '../vurdering-av-form/vurderingForm.css'; +import VurderingDokumentfilter from '../vurdering-dokumentfilter/VurderingDokumentfilter'; +import vurderingDokumentfilterOptions from '../vurdering-dokumentfilter/vurderingDokumentfilterOptions'; +import { finnBenyttedeDokumenter } from '../../../util/dokumentUtils'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyType = any; + +export enum FieldName { + VURDERING_LANGVARIG_SYKDOM = 'vurderingLangvarigSykdom', + HAR_LANGVARIG_SYKDOM = 'harLangvarigSykdom', + SPLITT_PERIODE_DATO = 'splittPeriodeDato', + DOKUMENTER = 'dokumenter', + PERIODER = 'perioder', +} + +const lagLangvarigSykdomVurdering = ( + formState: VurderingLangvarigSykdomFormState, + alleDokumenter: Dokument[], +): Partial => { + const resultat = formState[FieldName.HAR_LANGVARIG_SYKDOM]; + const tekst = formState[FieldName.VURDERING_LANGVARIG_SYKDOM]; + const dokumenter = finnBenyttedeDokumenter(formState[FieldName.DOKUMENTER], alleDokumenter); + const perioder = formState[FieldName.PERIODER].map( + periodeWrapper => new Period((periodeWrapper as AnyType).period.fom, (periodeWrapper as AnyType).period.tom), + ); + + return { + resultat, + tekst, + perioder, + dokumenter, + }; +}; + +export interface VurderingLangvarigSykdomFormState { + [FieldName.VURDERING_LANGVARIG_SYKDOM]?: string; + [FieldName.HAR_LANGVARIG_SYKDOM]?: Vurderingsresultat; + [FieldName.SPLITT_PERIODE_DATO]?: string; + [FieldName.DOKUMENTER]: string[]; + [FieldName.PERIODER]?: Period[]; +} + +interface VurderingLangvarigSykdomFormProps { + defaultValues: VurderingLangvarigSykdomFormState; + resterendeVurderingsperioder?: Period[]; + perioderSomKanVurderes?: Period[]; + onSubmit: (nyVurdering: Partial) => void; + dokumenter: Dokument[]; + onAvbryt: () => void; + isSubmitting: boolean; +} + +const VurderingLangvarigSykdomForm = ({ + defaultValues, + resterendeVurderingsperioder, + perioderSomKanVurderes, + onSubmit, + dokumenter, + onAvbryt, + isSubmitting, +}: VurderingLangvarigSykdomFormProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + const formMethods = useForm({ + defaultValues, + mode: 'onChange', + }); + const [visAlleDokumenter, setVisAlleDokumenter] = useState(false); + const [dokumentFilter, setDokumentFilter] = useState([]); + + const sammenhengendeSøknadsperioder = slåSammenSammenhengendePerioder(perioderSomKanVurderes); + + const avgrensningerForSøknadsperiode = React.useMemo( + () => finnMaksavgrensningerForPerioder(perioderSomKanVurderes), + [perioderSomKanVurderes], + ); + + const hullISøknadsperiodene = React.useMemo( + () => finnHullIPerioder(perioderSomKanVurderes).map(period => period.asInternationalPeriod()), + [perioderSomKanVurderes], + ); + + const updateDokumentFilter = valgtFilter => { + if (dokumentFilter.includes(valgtFilter)) { + if (dokumentFilter.length === 1) { + setVisAlleDokumenter(false); + } + setDokumentFilter(dokumentFilter.filter(v => v !== valgtFilter)); + } else { + setDokumentFilter(dokumentFilter.concat([valgtFilter])); + setVisAlleDokumenter(true); + } + }; + + const perioderSomBlirVurdert: any[] = useWatch({ control: formMethods.control, name: FieldName.PERIODER }); + + const harVurdertAlleDagerSomSkalVurderes = React.useMemo(() => { + const dagerSomSkalVurderes = (resterendeVurderingsperioder || []).flatMap(p => p.asListOfDays()); + const dagerSomBlirVurdert = (perioderSomBlirVurdert || []) + .map(period => { + if ((period as AnyType).period) { + return (period as AnyType).period; + } + return period; + }) + .flatMap(p => new Period(p.fom, p.tom).asListOfDays()); + return dagerSomSkalVurderes.every(dagSomSkalVurderes => dagerSomBlirVurdert.indexOf(dagSomSkalVurderes) > -1); + }, [resterendeVurderingsperioder, perioderSomBlirVurdert]); + + const getDokumenterSomSkalVises = () => { + const filtrerteDokumenter = dokumenter.filter(dokument => { + if (!dokumentFilter.length) { + return true; + } + return dokumentFilter.some(filter => dokument[filter] === true); + }); + + return filtrerteDokumenter.filter((dokument, index) => { + if (dokumentFilter.length === 0) { + if (dokumenter.length < 6) { + return true; + } + if (!visAlleDokumenter && index > 4) { + return false; + } + } + return true; + }); + }; + + const visFlereDokumenterKnapp = () => { + if (dokumentFilter.length > 0) { + return false; + } + if (dokumenter.length < 6) { + return false; + } + return true; + }; + + const lagNyLangvarigSykdomVurdering = (formState: VurderingLangvarigSykdomFormState) => { + onSubmit(lagLangvarigSykdomVurdering(formState, dokumenter)); + }; + + return ( + + + {visFlereDokumenterKnapp() && ( + + )} + + )} + + + {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} + + Gjør en vurdering av om den pleietrengende er i langvarig sykdom i perioden det søkes for, jamfør + folketrygdens § 9.13 + +

    + Du skal ta utgangspunkt i{' '} + + lovteksten + {' '} + og{' '} + + rundskrivet + {' '} + når du skriver vurderingen. +

    +
    + + } + validators={{ required }} + /> +
    + + + + + + { + const isOk = sammenhengendeSøknadsperioder.some(sammenhengendeSøknadsperiode => + sammenhengendeSøknadsperiode.covers(value), + ); + + if (!isOk) { + return 'Perioden som vurderes må være innenfor en eller flere sammenhengede søknadsperioder'; + } + + return true; + }, + fomDatoErFørTomDato, + }} + fromDatepickerProps={{ + label: 'Fra', + ariaLabel: 'fra', + limitations: { + minDate: avgrensningerForSøknadsperiode?.fom, + maxDate: avgrensningerForSøknadsperiode?.tom, + invalidDateRanges: hullISøknadsperiodene, + }, + }} + toDatepickerProps={{ + label: 'Til', + ariaLabel: 'til', + limitations: { + minDate: avgrensningerForSøknadsperiode?.fom, + maxDate: avgrensningerForSøknadsperiode?.tom, + invalidDateRanges: hullISøknadsperiodene, + }, + }} + renderContentAfterElement={(index, numberOfItems, fieldArrayMethods) => + numberOfItems > 1 && ( + { + fieldArrayMethods.remove(index); + }} + /> + ) + } + renderAfterFieldArray={fieldArrayMethods => ( + + fieldArrayMethods.append({ fom: '', tom: '' })} + id="leggTilPeriodeKnapp" + /> + + )} + /> + + {!harVurdertAlleDagerSomSkalVurderes && ( + + + Du har ikke vurdert alle periodene som må vurderes. Resterende perioder vurderer du etter at du har + lagret denne. + + + )} + + +
    + ); +}; + +export default VurderingLangvarigSykdomForm; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-livets-sluttfase-form/VurderingAvLivetsSluttfaseForm.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-livets-sluttfase-form/VurderingAvLivetsSluttfaseForm.tsx" new file mode 100644 index 0000000000..7762c3814c --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-livets-sluttfase-form/VurderingAvLivetsSluttfaseForm.tsx" @@ -0,0 +1,370 @@ +import { Close } from '@navikt/ds-icons'; +import { Alert, Label, Link } from '@navikt/ds-react'; +import { Box, ContentWithTooltip, Form, Margin, OnePersonOutlineGray } from '@navikt/ft-plattform-komponenter'; +import { CheckboxGroupRHF, PeriodpickerListRHF, RadioGroupPanelRHF, TextAreaRHF } from '@fpsak-frontend/form'; +import { Period } from '@fpsak-frontend/utils'; +import React, { useState } from 'react'; +import { FormProvider, useForm, useWatch } from 'react-hook-form'; +import Dokument from '../../../types/Dokument'; +import { Vurderingsversjon } from '../../../types/Vurdering'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import { + finnHullIPerioder, + finnMaksavgrensningerForPerioder, + slåSammenSammenhengendePerioder, +} from '../../../util/periodUtils'; +import ContainerContext from '../../context/ContainerContext'; +import { fomDatoErFørTomDato, harBruktDokumentasjon, required } from '../../form/validators'; +import AddButton from '../add-button/AddButton'; +import DeleteButton from '../delete-button/DeleteButton'; +import DetailViewVurdering from '../detail-view-vurdering/DetailViewVurdering'; +import DokumentLink from '../dokument-link/DokumentLink'; +import StjerneIkon from '../vurdering-av-form/StjerneIkon'; +import styles from '../vurdering-av-form/vurderingForm.css'; +import VurderingDokumentfilter from '../vurdering-dokumentfilter/VurderingDokumentfilter'; +import vurderingDokumentfilterOptions from '../vurdering-dokumentfilter/vurderingDokumentfilterOptions'; +import { finnBenyttedeDokumenter } from '../../../util/dokumentUtils'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyType = any; + +export enum FieldName { + VURDERING_AV_LIVETS_SLUTTFASE = 'vurderingAvLivetsSluttfase', + ER_I_LIVETS_SLUTTFASE = 'erILivetsSluttfase', + SPLITT_PERIODE_DATO = 'splittPeriodeDato', + DOKUMENTER = 'dokumenter', + PERIODER = 'perioder', +} + +const lagSluttfaseVurdering = ( + formState: VurderingAvLivetsSluttfaseFormState, + alleDokumenter: Dokument[], +): Partial => { + const resultat = formState[FieldName.ER_I_LIVETS_SLUTTFASE]; + const tekst = formState[FieldName.VURDERING_AV_LIVETS_SLUTTFASE]; + const dokumenter = finnBenyttedeDokumenter(formState[FieldName.DOKUMENTER], alleDokumenter); + const perioder = formState[FieldName.PERIODER].map( + periodeWrapper => new Period((periodeWrapper as AnyType).period.fom, (periodeWrapper as AnyType).period.tom), + ); + + return { + resultat, + tekst, + perioder, + dokumenter, + }; +}; + +export interface VurderingAvLivetsSluttfaseFormState { + [FieldName.VURDERING_AV_LIVETS_SLUTTFASE]?: string; + [FieldName.ER_I_LIVETS_SLUTTFASE]?: Vurderingsresultat; + [FieldName.SPLITT_PERIODE_DATO]?: string; + [FieldName.DOKUMENTER]: string[]; + [FieldName.PERIODER]?: Period[]; +} + +interface VurderingAvLivetsSluttfaseFormProps { + defaultValues: VurderingAvLivetsSluttfaseFormState; + resterendeVurderingsperioder?: Period[]; + perioderSomKanVurderes?: Period[]; + onSubmit: (nyVurdering: Partial) => void; + dokumenter: Dokument[]; + onAvbryt: () => void; + isSubmitting: boolean; +} + +const VurderingAvLivetsSluttfaseForm = ({ + defaultValues, + resterendeVurderingsperioder, + perioderSomKanVurderes, + onSubmit, + dokumenter, + onAvbryt, + isSubmitting, +}: VurderingAvLivetsSluttfaseFormProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + const formMethods = useForm({ + defaultValues, + mode: 'onChange', + }); + const [visAlleDokumenter, setVisAlleDokumenter] = useState(false); + const [dokumentFilter, setDokumentFilter] = useState([]); + + const sammenhengendeSøknadsperioder = slåSammenSammenhengendePerioder(perioderSomKanVurderes); + + const avgrensningerForSøknadsperiode = React.useMemo( + () => finnMaksavgrensningerForPerioder(perioderSomKanVurderes), + [perioderSomKanVurderes], + ); + + const hullISøknadsperiodene = React.useMemo( + () => finnHullIPerioder(perioderSomKanVurderes).map(period => period.asInternationalPeriod()), + [perioderSomKanVurderes], + ); + + const updateDokumentFilter = valgtFilter => { + if (dokumentFilter.includes(valgtFilter)) { + if (dokumentFilter.length === 1) { + setVisAlleDokumenter(false); + } + setDokumentFilter(dokumentFilter.filter(v => v !== valgtFilter)); + } else { + setDokumentFilter(dokumentFilter.concat([valgtFilter])); + setVisAlleDokumenter(true); + } + }; + + const perioderSomBlirVurdert: any[] = useWatch({ control: formMethods.control, name: FieldName.PERIODER }); + + const harVurdertAlleDagerSomSkalVurderes = React.useMemo(() => { + const dagerSomSkalVurderes = (resterendeVurderingsperioder || []).flatMap(p => p.asListOfDays()); + const dagerSomBlirVurdert = (perioderSomBlirVurdert || []) + .map(period => { + if ((period as AnyType).period) { + return (period as AnyType).period; + } + return period; + }) + .flatMap(p => new Period(p.fom, p.tom).asListOfDays()); + return dagerSomSkalVurderes.every(dagSomSkalVurderes => dagerSomBlirVurdert.indexOf(dagSomSkalVurderes) > -1); + }, [resterendeVurderingsperioder, perioderSomBlirVurdert]); + + const getDokumenterSomSkalVises = () => { + const filtrerteDokumenter = dokumenter.filter(dokument => { + if (!dokumentFilter.length) { + return true; + } + return dokumentFilter.some(filter => dokument[filter] === true); + }); + + return filtrerteDokumenter.filter((dokument, index) => { + if (dokumentFilter.length === 0) { + if (dokumenter.length < 6) { + return true; + } + if (!visAlleDokumenter && index > 4) { + return false; + } + } + return true; + }); + }; + + const visFlereDokumenterKnapp = () => { + if (dokumentFilter.length > 0) { + return false; + } + if (dokumenter.length < 6) { + return false; + } + return true; + }; + + const lagNySluttfaseVurdering = (formState: VurderingAvLivetsSluttfaseFormState) => { + onSubmit(lagSluttfaseVurdering(formState, dokumenter)); + }; + + return ( + + + {visFlereDokumenterKnapp() && ( + + )} + + )} + + + {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} + + Gjør en vurdering av om den pleietrengende er i livets sluttfase i perioden det søkes for, jamfør + folketrygdens § 9.13 + +

    + Du skal ta utgangspunkt i{' '} + + lovteksten + {' '} + og{' '} + + rundskrivet + {' '} + når du skriver vurderingen. +

    +
    + + } + validators={{ required }} + /> +
    + + + + + + { + const isOk = sammenhengendeSøknadsperioder.some(sammenhengendeSøknadsperiode => + sammenhengendeSøknadsperiode.covers(value), + ); + + if (!isOk) { + return 'Perioden som vurderes må være innenfor en eller flere sammenhengede søknadsperioder'; + } + + return true; + }, + fomDatoErFørTomDato, + }} + fromDatepickerProps={{ + label: 'Fra', + ariaLabel: 'fra', + limitations: { + minDate: avgrensningerForSøknadsperiode?.fom, + maxDate: avgrensningerForSøknadsperiode?.tom, + invalidDateRanges: hullISøknadsperiodene, + }, + }} + toDatepickerProps={{ + label: 'Til', + ariaLabel: 'til', + limitations: { + minDate: avgrensningerForSøknadsperiode?.fom, + maxDate: avgrensningerForSøknadsperiode?.tom, + invalidDateRanges: hullISøknadsperiodene, + }, + }} + renderContentAfterElement={(index, numberOfItems, fieldArrayMethods) => + numberOfItems > 1 && ( + { + fieldArrayMethods.remove(index); + }} + /> + ) + } + renderAfterFieldArray={fieldArrayMethods => ( + + fieldArrayMethods.append({ fom: '', tom: '' })} + id="leggTilPeriodeKnapp" + /> + + )} + /> + + {!harVurdertAlleDagerSomSkalVurderes && ( + + + Du har ikke vurdert alle periodene som må vurderes. Resterende perioder vurderer du etter at du har + lagret denne. + + + )} + + +
    + ); +}; + +export default VurderingAvLivetsSluttfaseForm; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-tilsynsbehov-form/VurderingAvTilsynsbehovForm.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-tilsynsbehov-form/VurderingAvTilsynsbehovForm.tsx" new file mode 100644 index 0000000000..22a14afd41 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-tilsynsbehov-form/VurderingAvTilsynsbehovForm.tsx" @@ -0,0 +1,388 @@ +import { Close } from '@navikt/ds-icons'; +import { Alert, Label, Link } from '@navikt/ds-react'; +import { Box, ContentWithTooltip, Form, Margin, OnePersonOutlineGray } from '@navikt/ft-plattform-komponenter'; +import { isSameOrBefore, Period } from '@fpsak-frontend/utils'; +import { CheckboxGroupRHF, PeriodpickerListRHF, TextAreaRHF, YesOrNoQuestionRHF } from '@fpsak-frontend/form'; +import React, { useState } from 'react'; +import { FormProvider, useForm, useWatch } from 'react-hook-form'; +import Dokument from '../../../types/Dokument'; +import { Vurderingsversjon } from '../../../types/Vurdering'; +import { + finnHullIPerioder, + finnMaksavgrensningerForPerioder, + slåSammenSammenhengendePerioder, +} from '../../../util/periodUtils'; +import ContainerContext from '../../context/ContainerContext'; +import { fomDatoErFørTomDato, harBruktDokumentasjon, required } from '../../form/validators'; +import AddButton from '../add-button/AddButton'; +import DeleteButton from '../delete-button/DeleteButton'; +import DetailViewVurdering from '../detail-view-vurdering/DetailViewVurdering'; +import DokumentLink from '../dokument-link/DokumentLink'; +import VurderingDokumentfilter from '../vurdering-dokumentfilter/VurderingDokumentfilter'; +import vurderingDokumentfilterOptions from '../vurdering-dokumentfilter/vurderingDokumentfilterOptions'; +import StjerneIkon from '../vurdering-av-form/StjerneIkon'; +import styles from '../vurdering-av-form/vurderingForm.css'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import { finnBenyttedeDokumenter } from '../../../util/dokumentUtils'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyType = any; + +export enum FieldName { + VURDERING_AV_KONTINUERLIG_TILSYN_OG_PLEIE = 'vurderingAvKontinuerligTilsynOgPleie', + HAR_BEHOV_FOR_KONTINUERLIG_TILSYN_OG_PLEIE = 'harBehovForKontinuerligTilsynOgPleie', + PERIODER = 'perioder', + DOKUMENTER = 'dokumenter', +} + +const lagTilsynsbehovVurdering = ( + formState: VurderingAvTilsynsbehovFormState, + alleDokumenter: Dokument[], +): Partial => { + const resultat = formState[FieldName.HAR_BEHOV_FOR_KONTINUERLIG_TILSYN_OG_PLEIE] + ? Vurderingsresultat.OPPFYLT + : Vurderingsresultat.IKKE_OPPFYLT; + + const perioder = formState[FieldName.PERIODER].map( + periodeWrapper => new Period((periodeWrapper as AnyType).period.fom, (periodeWrapper as AnyType).period.tom), + ); + const begrunnelse = formState[FieldName.VURDERING_AV_KONTINUERLIG_TILSYN_OG_PLEIE]; + + return { + resultat, + perioder, + tekst: begrunnelse, + dokumenter: finnBenyttedeDokumenter(formState[FieldName.DOKUMENTER], alleDokumenter), + }; +}; + +export interface VurderingAvTilsynsbehovFormState { + [FieldName.VURDERING_AV_KONTINUERLIG_TILSYN_OG_PLEIE]?: string; + [FieldName.HAR_BEHOV_FOR_KONTINUERLIG_TILSYN_OG_PLEIE]?: boolean; + [FieldName.PERIODER]?: Period[]; + [FieldName.DOKUMENTER]: string[]; +} + +interface VurderingAvTilsynsbehovFormProps { + defaultValues: VurderingAvTilsynsbehovFormState; + onSubmit: (nyVurdering: Partial) => void; + resterendeVurderingsperioder?: Period[]; + perioderSomKanVurderes?: Period[]; + dokumenter: Dokument[]; + onAvbryt: () => void; + isSubmitting: boolean; + harPerioderDerPleietrengendeErOver18år?: boolean; + barnetsAttenårsdag?: string; +} + +const VurderingAvTilsynsbehovForm = ({ + defaultValues, + onSubmit, + resterendeVurderingsperioder, + perioderSomKanVurderes, + dokumenter, + onAvbryt, + isSubmitting, + harPerioderDerPleietrengendeErOver18år, + barnetsAttenårsdag, +}: VurderingAvTilsynsbehovFormProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + const formMethods = useForm({ + defaultValues, + mode: 'onChange', + }); + const [visAlleDokumenter, setVisAlleDokumenter] = useState(false); + const [dokumentFilter, setDokumentFilter] = useState([]); + + const updateDokumentFilter = valgtFilter => { + if (dokumentFilter.includes(valgtFilter)) { + if (dokumentFilter.length === 1) { + setVisAlleDokumenter(false); + } + setDokumentFilter(dokumentFilter.filter(v => v !== valgtFilter)); + } else { + setDokumentFilter(dokumentFilter.concat([valgtFilter])); + setVisAlleDokumenter(true); + } + }; + + const getDokumenterSomSkalVises = () => { + const filtrerteDokumenter = dokumenter.filter(dokument => { + if (!dokumentFilter.length) { + return true; + } + return dokumentFilter.some(filter => dokument[filter] === true); + }); + + return filtrerteDokumenter.filter((dokument, index) => { + if (dokumentFilter.length === 0) { + if (dokumenter.length < 6) { + return true; + } + if (!visAlleDokumenter && index > 4) { + return false; + } + } + return true; + }); + }; + + const visFlereDokumenterKnapp = () => { + if (dokumentFilter.length > 0) { + return false; + } + if (dokumenter.length < 6) { + return false; + } + return true; + }; + + const perioderSomBlirVurdert: Period[] = useWatch({ control: formMethods.control, name: FieldName.PERIODER }); + const harVurdertAlleDagerSomSkalVurderes = React.useMemo(() => { + const dagerSomSkalVurderes = (resterendeVurderingsperioder || []).flatMap(p => p.asListOfDays()); + const dagerSomBlirVurdert = (perioderSomBlirVurdert || []) + .map(period => { + if ((period as AnyType).period) { + return (period as AnyType).period; + } + return period; + }) + .flatMap(p => new Period(p.fom, p.tom).asListOfDays()); + return dagerSomSkalVurderes.every(dagSomSkalVurderes => dagerSomBlirVurdert.indexOf(dagSomSkalVurderes) > -1); + }, [resterendeVurderingsperioder, perioderSomBlirVurdert]); + + const visLovparagrafForPleietrengendeOver18år = React.useMemo( + () => + harPerioderDerPleietrengendeErOver18år && + perioderSomBlirVurdert.some(periode => { + if ((periode as AnyType).period) { + return isSameOrBefore(barnetsAttenårsdag, (periode as AnyType).period.fom); + } + return isSameOrBefore(barnetsAttenårsdag, periode.fom); + }), + [perioderSomBlirVurdert], + ); + + const hullISøknadsperiodene = React.useMemo( + () => finnHullIPerioder(perioderSomKanVurderes).map(period => period.asInternationalPeriod()), + [perioderSomKanVurderes], + ); + + const avgrensningerForSøknadsperiode = React.useMemo( + () => finnMaksavgrensningerForPerioder(perioderSomKanVurderes), + [perioderSomKanVurderes], + ); + + const lagNyTilsynsvurdering = (formState: VurderingAvTilsynsbehovFormState) => { + onSubmit(lagTilsynsbehovVurdering(formState, dokumenter)); + }; + + const sammenhengendeSøknadsperioder = slåSammenSammenhengendePerioder(perioderSomKanVurderes); + + return ( + + + {visFlereDokumenterKnapp() && ( + + )} + + )} + + + {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} + + {visLovparagrafForPleietrengendeOver18år + ? `Gjør en vurdering av om det er behov for kontinuerlig tilsyn og pleie som følge + av sykdommen etter § 9-10, tredje ledd.` + : `Gjør en vurdering av om det er behov for kontinuerlig tilsyn og pleie som følge + av sykdommen etter § 9-10, første ledd.`} + +

    + Du skal ta utgangspunkt i{' '} + + lovteksten + {' '} + og{' '} + + rundskrivet + {' '} + når du skriver vurderingen. +

    + +

    Vurderingen skal beskrive:

    +
      +
    • Om det er årsakssammenheng mellom sykdom og pleiebehov
    • +
    • Om behovet er kontinuerlig og ikke situasjonsbestemt
    • +
    + + } + validators={{ required }} + /> +
    + + + + + { + const isOk = sammenhengendeSøknadsperioder.some(sammenhengendeSøknadsperiode => + sammenhengendeSøknadsperiode.covers(value), + ); + + if (!isOk) { + return 'Perioden som vurderes må være innenfor en eller flere sammenhengede søknadsperioder'; + } + + return true; + }, + fomDatoErFørTomDato, + }} + fromDatepickerProps={{ + label: 'Fra', + ariaLabel: 'fra', + limitations: { + minDate: avgrensningerForSøknadsperiode?.fom, + maxDate: avgrensningerForSøknadsperiode?.tom, + invalidDateRanges: hullISøknadsperiodene, + }, + }} + toDatepickerProps={{ + label: 'Til', + ariaLabel: 'til', + limitations: { + minDate: avgrensningerForSøknadsperiode?.fom, + maxDate: avgrensningerForSøknadsperiode?.tom, + invalidDateRanges: hullISøknadsperiodene, + }, + }} + renderContentAfterElement={(index, numberOfItems, fieldArrayMethods) => + numberOfItems > 1 && ( + { + fieldArrayMethods.remove(index); + }} + /> + ) + } + renderAfterFieldArray={fieldArrayMethods => ( + + fieldArrayMethods.append({ fom: '', tom: '' })} + id="leggTilPeriodeKnapp" + /> + + )} + /> + + {!harVurdertAlleDagerSomSkalVurderes && ( + + + Du har ikke vurdert alle periodene som må vurderes. Resterende perioder vurderer du etter at du har + lagret denne. + + + )} + + +
    + ); +}; + +export default VurderingAvTilsynsbehovForm; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-to-omsorgspersoner-form/VurderingAvToOmsorgspersonerForm.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-to-omsorgspersoner-form/VurderingAvToOmsorgspersonerForm.tsx" new file mode 100644 index 0000000000..ffa3f9a42e --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-av-to-omsorgspersoner-form/VurderingAvToOmsorgspersonerForm.tsx" @@ -0,0 +1,272 @@ +import { Alert, Link } from '@navikt/ds-react'; +import { Box, ContentWithTooltip, Form, Margin, OnePersonOutlineGray } from '@navikt/ft-plattform-komponenter'; +import { CheckboxGroupRHF, PeriodpickerListRHF, TextAreaRHF, YesOrNoQuestionRHF } from '@fpsak-frontend/form'; +import { Period } from '@fpsak-frontend/utils'; +import React from 'react'; +import { FormProvider, useForm, useWatch } from 'react-hook-form'; +import Dokument from '../../../types/Dokument'; +import { Vurderingsversjon } from '../../../types/Vurdering'; +import { + finnHullIPerioder, + finnMaksavgrensningerForPerioder, + slåSammenSammenhengendePerioder, +} from '../../../util/periodUtils'; +import ContainerContext from '../../context/ContainerContext'; +import { fomDatoErFørTomDato, harBruktDokumentasjon, required } from '../../form/validators'; +import AddButton from '../add-button/AddButton'; +import DeleteButton from '../delete-button/DeleteButton'; +import DetailViewVurdering from '../detail-view-vurdering/DetailViewVurdering'; +import DokumentLink from '../dokument-link/DokumentLink'; +import styles from '../vurdering-av-form/vurderingForm.css'; +import { finnBenyttedeDokumenter } from '../../../util/dokumentUtils'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyType = any; + +export enum FieldName { + VURDERING_AV_TO_OMSORGSPERSONER = 'vurderingAvToOmsorgspersoner', + HAR_BEHOV_FOR_TO_OMSORGSPERSONER = 'harBehovForToOmsorgspersoner', + PERIODER = 'perioder', + DOKUMENTER = 'dokumenter', +} + +const lagToOmsorgspersonerVurdering = ( + formState: VurderingAvToOmsorgspersonerFormState, + alleDokumenter: Dokument[], +): Partial => { + const resultat = formState[FieldName.HAR_BEHOV_FOR_TO_OMSORGSPERSONER] + ? Vurderingsresultat.OPPFYLT + : Vurderingsresultat.IKKE_OPPFYLT; + const perioder = formState[FieldName.PERIODER].map( + periodeWrapper => new Period((periodeWrapper as AnyType).period.fom, (periodeWrapper as AnyType).period.tom), + ); + const begrunnelse = formState[FieldName.VURDERING_AV_TO_OMSORGSPERSONER]; + + return { + resultat, + perioder, + tekst: begrunnelse, + dokumenter: finnBenyttedeDokumenter(formState[FieldName.DOKUMENTER], alleDokumenter), + }; +}; + +export interface VurderingAvToOmsorgspersonerFormState { + [FieldName.VURDERING_AV_TO_OMSORGSPERSONER]?: string; + [FieldName.HAR_BEHOV_FOR_TO_OMSORGSPERSONER]?: boolean; + [FieldName.PERIODER]?: Period[]; + [FieldName.DOKUMENTER]: string[]; +} + +interface VurderingAvToOmsorgspersonerFormProps { + defaultValues: VurderingAvToOmsorgspersonerFormState; + onSubmit: (nyVurdering: Partial) => void; + resterendeVurderingsperioder?: Period[]; + perioderSomKanVurderes?: Period[]; + dokumenter: Dokument[]; + onAvbryt: () => void; + isSubmitting: boolean; +} + +const VurderingAvToOmsorgspersonerForm = ({ + defaultValues, + onSubmit, + resterendeVurderingsperioder, + perioderSomKanVurderes, + dokumenter, + onAvbryt, + isSubmitting, +}: VurderingAvToOmsorgspersonerFormProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + + const formMethods = useForm({ + defaultValues, + }); + + const perioderSomBlirVurdert: Period[] = useWatch({ control: formMethods.control, name: FieldName.PERIODER }); + + const harVurdertAlleDagerSomSkalVurderes = React.useMemo(() => { + const dagerSomSkalVurderes = (resterendeVurderingsperioder || []).flatMap(period => period.asListOfDays()); + const dagerSomBlirVurdert = (perioderSomBlirVurdert || []) + .map(period => { + if ((period as AnyType).period) { + return (period as AnyType).period; + } + return period; + }) + .flatMap(p => new Period(p.fom, p.tom).asListOfDays()); + return dagerSomSkalVurderes.every(dagSomSkalVurderes => dagerSomBlirVurdert.indexOf(dagSomSkalVurderes) > -1); + }, [resterendeVurderingsperioder, perioderSomBlirVurdert]); + + const hullISøknadsperiodene = React.useMemo( + () => finnHullIPerioder(perioderSomKanVurderes).map(period => period.asInternationalPeriod()), + [perioderSomKanVurderes], + ); + + const avgrensningerForSøknadsperiode = React.useMemo( + () => finnMaksavgrensningerForPerioder(perioderSomKanVurderes), + [perioderSomKanVurderes], + ); + + const lagNyVurdering = (formState: VurderingAvToOmsorgspersonerFormState) => { + onSubmit(lagToOmsorgspersonerVurdering(formState, dokumenter)); + }; + + const sammenhengendePerioderMedTilsynsbehov = React.useMemo( + () => slåSammenSammenhengendePerioder(perioderSomKanVurderes), + [perioderSomKanVurderes], + ); + + return ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + +
    + {dokumenter?.length > 0 && ( + + ({ + value: dokument.id, + label: ( + + + + ) + } + /> + ), + }))} + validators={{ + harBruktDokumentasjon, + }} + disabled={readOnly} + /> + + )} + + + Gjør en vurdering av om det er behov for to omsorgspersoner samtidig etter § 9-10, andre ledd. +

    + Du skal ta utgangspunkt i{' '} + + lovteksten + {' '} + og{' '} + + rundskrivet + {' '} + når du skriver vurderingen. +

    +

    Vurderingen skal beskrive:

    +
      +
    • Om tilsyn og pleie kan ivaretas av én person alene i hele eller deler av perioden.
    • +
    + + } + validators={{ required }} + disabled={readOnly} + id="begrunnelsesfelt" + /> +
    + + + + + { + const isOk = sammenhengendePerioderMedTilsynsbehov.some(sammenhengendeSøknadsperiode => + sammenhengendeSøknadsperiode.covers(value), + ); + + if (!isOk) { + return 'Perioden som vurderes må være innenfor en eller flere sammenhengede perioder med behov for kontinuerlig tilsyn og pleie'; + } + + return true; + }, + fomDatoErFørTomDato, + }} + fromDatepickerProps={{ + label: 'Fra', + ariaLabel: 'fra', + limitations: { + minDate: avgrensningerForSøknadsperiode?.fom || '', + maxDate: avgrensningerForSøknadsperiode?.tom || '', + invalidDateRanges: hullISøknadsperiodene, + }, + }} + toDatepickerProps={{ + label: 'Til', + ariaLabel: 'til', + limitations: { + minDate: avgrensningerForSøknadsperiode?.fom || '', + maxDate: avgrensningerForSøknadsperiode?.tom || '', + invalidDateRanges: hullISøknadsperiodene, + }, + }} + renderContentAfterElement={(index, numberOfItems, fieldArrayMethods) => + numberOfItems > 1 && ( + { + fieldArrayMethods.remove(index); + }} + /> + ) + } + renderAfterFieldArray={fieldArrayMethods => ( + + fieldArrayMethods.append({ fom: '', tom: '' })} + id="leggTilPeriodeKnapp" + /> + + )} + /> + + {!harVurdertAlleDagerSomSkalVurderes && ( + + + Du har ikke vurdert alle periodene som må vurderes. Resterende perioder vurderer du etter at du har + lagret denne. + + + )} +
    +
    +
    + ); +}; + +export default VurderingAvToOmsorgspersonerForm; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-dokumentfilter/VurderingDokumentfilter.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-dokumentfilter/VurderingDokumentfilter.tsx" new file mode 100644 index 0000000000..da83e7fab9 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-dokumentfilter/VurderingDokumentfilter.tsx" @@ -0,0 +1,65 @@ +import { Collapse, Expand, FilterFilled } from '@navikt/ds-icons'; +import { Checkbox, Label } from '@navikt/ds-react'; +import classNames from 'classnames'; +import React, { useState } from 'react'; +import OutsideClickHandler from 'react-outside-click-handler'; +import styles from './vurderingDokumentfilter.css'; +import vurderingDokumentfilterOptions from './vurderingDokumentfilterOptions'; + +interface ChevronWithTextProps { + chevronDirection: 'opp' | 'ned'; + onClick: () => void; + text: string; +} +interface VurderingDokumentfilterProps { + text: string; + filters: string[]; + onFilterChange: (value: string) => void; +} + +const ChevronWithText = ({ chevronDirection, onClick, text }: ChevronWithTextProps): JSX.Element => ( + +); + +const VurderingDokumentfilter = ({ text, filters, onFilterChange }: VurderingDokumentfilterProps): JSX.Element => { + const [open, setOpen] = useState(false); + const chevronDirection = open ? 'opp' : 'ned'; + const filterListe = vurderingDokumentfilterOptions; + const listeErFiltrert = filters.length > 0; + return ( +
    + + setOpen(!open)} text={text} /> + + + {open && ( + setOpen(false)}> +
    + + setOpen(!open)} text={text} /> + +
    + {filterListe.map(({ label, attributtNavn }) => ( + onFilterChange(attributtNavn)} + > + {label} + + ))} +
    +
    +
    +
    + )} +
    + ); +}; + +export default VurderingDokumentfilter; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-dokumentfilter/vurderingDokumentfilter.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-dokumentfilter/vurderingDokumentfilter.css" new file mode 100644 index 0000000000..1c6f708dac --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-dokumentfilter/vurderingDokumentfilter.css" @@ -0,0 +1,32 @@ +.vurderingDokumentfilter { + margin-right: -2.25rem; +} + +.chevronDropdown__dropdown { + position: absolute; + top: -0.875rem; + left: -1.3125rem; + border: 1px solid black; + border-radius: 0.1875rem; + padding: 0.8125rem 0.8125rem 0.8125rem 1rem; + margin-left: 0.25rem; + z-index: 1; + background-color: white; +} + +.chevronDropdown__toggleButton { + border: none; + background: none; + display: inline-flex; + padding: unset; +} + +.chevronDropdown__toggleButton__text { + display: inline; + line-height: 1.1875rem; + margin-right: 0.375rem; +} + +.chevronDropdown__hidden { + visibility: hidden; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-dokumentfilter/vurderingDokumentfilterOptions.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-dokumentfilter/vurderingDokumentfilterOptions.ts" new file mode 100644 index 0000000000..c5af67b526 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurdering-dokumentfilter/vurderingDokumentfilterOptions.ts" @@ -0,0 +1,6 @@ +const vurderingDokumentfilterOptions = [ + { label: 'Tidligere brukte dokumenter', attributtNavn: 'bruktTilMinstEnVurdering' }, + { label: 'Dokument fra annen part', attributtNavn: 'annenPartErKilde' }, +]; + +export default vurderingDokumentfilterOptions; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljer-fetcher/VurderingsdetaljerFetcher.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljer-fetcher/VurderingsdetaljerFetcher.tsx" new file mode 100644 index 0000000000..e117207e08 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljer-fetcher/VurderingsdetaljerFetcher.tsx" @@ -0,0 +1,61 @@ +import { Loader } from '@navikt/ds-react'; +import { PageError } from '@navikt/ft-plattform-komponenter'; +import { httpUtils } from '@fpsak-frontend/utils'; +import React, { useMemo } from 'react'; +import Vurdering from '../../../types/Vurdering'; +import ContainerContext from '../../context/ContainerContext'; + +interface VurderingsdetaljerFetcherProps { + url: string; + contentRenderer: (vurdering: Vurdering) => JSX.Element; +} + +const VurderingsdetaljerFetcher = ({ url, contentRenderer }: VurderingsdetaljerFetcherProps): JSX.Element => { + const { httpErrorHandler } = React.useContext(ContainerContext); + + const [isLoading, setIsLoading] = React.useState(true); + const [vurdering, setVurdering] = React.useState(null); + const [hentVurderingHarFeilet, setHentVurderingHarFeilet] = React.useState(false); + + const controller = useMemo(() => new AbortController(), [url]); + + function hentVurderingsdetaljer(): Promise { + return httpUtils.get(url, httpErrorHandler, { signal: controller.signal }); + } + + const handleError = () => { + setIsLoading(false); + setHentVurderingHarFeilet(true); + }; + + React.useEffect(() => { + let isMounted = true; + setIsLoading(true); + setHentVurderingHarFeilet(false); + hentVurderingsdetaljer() + .then((vurderingResponse: Vurdering) => { + const fetchedVurdering = new Vurdering(vurderingResponse); + if (isMounted) { + setVurdering(fetchedVurdering); + setIsLoading(false); + } + }) + .catch(handleError); + + return () => { + isMounted = false; + controller.abort(); + }; + }, [url]); + + if (isLoading) { + return ; + } + if (hentVurderingHarFeilet) { + return ; + } + + return contentRenderer(vurdering); +}; + +export default VurderingsdetaljerFetcher; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljer-fetcher/tests/VurderingsdetaljerFetcher.spec.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljer-fetcher/tests/VurderingsdetaljerFetcher.spec.tsx" new file mode 100644 index 0000000000..148b5f56bc --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljer-fetcher/tests/VurderingsdetaljerFetcher.spec.tsx" @@ -0,0 +1,78 @@ +import { httpUtils } from '@fpsak-frontend/utils'; +import { render, waitFor, screen } from '@testing-library/react'; +import React from 'react'; +import ContainerContext from '../../../context/ContainerContext'; +import VurderingsdetaljerFetcher from '../VurderingsdetaljerFetcher'; + +const mockedContent = 'mockedContent'; +const mockedUrl = 'mockedUrl'; +const httpErrorHandlerMock = () => null; + +const vurderingMock = { + versjoner: [], + annenInformasjon: { + resterendeVurderingsperioder: [], + perioderSomKanVurderes: [], + }, +}; + +const contextWrapper = ui => + render( + + {ui} + , + ); + +const renderVurderingsdetaljerFetcher = () => + contextWrapper( {mockedContent}} />); + +describe('VurderingsdetaljerFetcher', () => { + let httpGetSpy = null; + + beforeAll(() => { + httpGetSpy = jest.spyOn(httpUtils, 'get'); + }); + + const mockResolvedGetApiCallOnce = data => { + httpGetSpy.mockImplementationOnce( + () => + new Promise(resolve => { + resolve(data); + }), + ); + }; + + const mockRejectedGetApiCallOnce = () => { + httpGetSpy.mockImplementationOnce( + () => + new Promise((resolve, reject) => { + reject(); + }), + ); + }; + + it('should render a spinner while data is being fetched, and render specified content after data has been recieved', async () => { + mockResolvedGetApiCallOnce(vurderingMock); + const { getByText } = renderVurderingsdetaljerFetcher(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + expect(screen.queryByText(mockedContent)).toBeNull(); + await waitFor(() => expect(getByText(mockedContent)).toBeInTheDocument()); + }); + + it('should render a spinner while data is being fetched, and render an error message when http call has failed', async () => { + mockRejectedGetApiCallOnce(); + const { getByText, queryByText } = renderVurderingsdetaljerFetcher(); + expect(getByText(/Venter.../i)).toBeInTheDocument(); + expect(queryByText(mockedContent)).toBeNull(); + await waitFor(() => { + expect(getByText(/Noe gikk galt, vennligst prøv igjen senere/i)).toBeInTheDocument(); + expect(queryByText(mockedContent)).toBeNull(); + }); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljer/Vurderingsdetaljer.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljer/Vurderingsdetaljer.tsx" new file mode 100644 index 0000000000..dd1121c4ab --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljer/Vurderingsdetaljer.tsx" @@ -0,0 +1,66 @@ +import React from 'react'; +import Vurderingselement from '../../../types/Vurderingselement'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +// eslint-disable-next-line max-len +import VurderingsdetaljvisningForEksisterendeVurdering from '../vurderingsdetaljvisning-for-eksisterende-vurdering/VurderingsdetaljvisningForEksisterendeVurdering'; +import VurderingsdetaljvisningForNyVurdering from '../vurderingsdetaljvisning-for-ny-vurdering/VurderingsdetaljvisningForNyVurdering'; + +interface VurderingsdetaljerProps { + valgtVurderingselement: Vurderingselement; + vurderingsoversikt: Vurderingsoversikt; + onVurderingLagret: () => void; + onAvbryt: () => void; + radForNyVurderingVises: boolean; + nyVurderingFormVises: boolean; +} + +const Vurderingsdetaljer = ({ + valgtVurderingselement, + vurderingsoversikt, + onVurderingLagret, + onAvbryt, + radForNyVurderingVises, + nyVurderingFormVises, +}: VurderingsdetaljerProps): JSX.Element => { + const [editMode, setEditMode] = React.useState(false); + + React.useEffect(() => { + setEditMode(false); + }, [valgtVurderingselement]); + + const onVurderingLagretCallback = () => { + setEditMode(false); + onVurderingLagret(); + }; + + let valgtVurderingContent = null; + if (valgtVurderingselement) { + valgtVurderingContent = ( + setEditMode(true)} + onAvbrytClick={() => setEditMode(false)} + onVurderingLagret={onVurderingLagretCallback} + /> + ); + } + + const harValgtVurderingselement = !!valgtVurderingselement; + return ( + <> + {harValgtVurderingselement && valgtVurderingContent} +
    + +
    + + ); +}; + +export default Vurderingsdetaljer; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljvisning-for-eksisterende-vurdering/VurderingsdetaljvisningForEksisterendeVurdering.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljvisning-for-eksisterende-vurdering/VurderingsdetaljvisningForEksisterendeVurdering.tsx" new file mode 100644 index 0000000000..b78790e8b9 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljvisning-for-eksisterende-vurdering/VurderingsdetaljvisningForEksisterendeVurdering.tsx" @@ -0,0 +1,158 @@ +import React from 'react'; +import { addYearsToDate } from '@fpsak-frontend/utils'; +import Vurderingselement from '../../../types/Vurderingselement'; +// eslint-disable-next-line max-len +import VurderingsoppsummeringForKontinuerligTilsynOgPleie from '../vurderingsoppsummering-for-kontinuerlig-tilsyn-og-pleie/VurderingsoppsummeringForKontinuerligTilsynOgPleie'; +import VurderingsdetaljerFetcher from '../vurderingsdetaljer-fetcher/VurderingsdetaljerFetcher'; +import LinkRel from '../../../constants/LinkRel'; +import { findHrefByRel, findLinkByRel } from '../../../util/linkUtils'; +import ManuellVurdering from '../../../types/ManuellVurdering'; +import buildInitialFormStateForEdit from '../vilkårsvurdering-av-tilsyn-og-pleie/initialFormStateUtil'; +import VurderingAvTilsynsbehovForm from '../vurdering-av-tilsynsbehov-form/VurderingAvTilsynsbehovForm'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import EndreVurderingController from '../endre-vurdering-controller/EndreVurderingController'; +import ContainerContext from '../../context/ContainerContext'; +import VurderingContext from '../../context/VurderingContext'; +import Vurderingstype from '../../../types/Vurderingstype'; +import VurderingAvToOmsorgspersonerForm from '../vurdering-av-to-omsorgspersoner-form/VurderingAvToOmsorgspersonerForm'; +import VurderingsoppsummeringForToOmsorgspersoner from '../vurderingsoppsummering-for-to-omsorgspersoner/VurderingsoppsummeringForToOmsorgspersoner'; +import VurderingsoppsummeringForInnleggelsesperiode from '../vurderingsoppsummering-for-innleggelsesperiode/VurderingsoppsummeringForInnleggelsesperiode'; +import InnleggelsesperiodeVurdering from '../../../types/InnleggelsesperiodeVurdering'; +import VurderingAvLivetsSluttfaseForm from '../vurdering-av-livets-sluttfase-form/VurderingAvLivetsSluttfaseForm'; +import VurderingsoppsummeringForSluttfase from '../vurderingsoppsummering-for-livets-sluttfase/VurderingsoppsummeringForSluttfase'; +import VurderingsoppsummeringLangvarigSykdom from '../vurderingsoppsummering-for-langvarig-sykdom/VurderingsoppsummeringLangvarigSykdom'; +import VurderingLangvarigSykdomForm from '../vurdering-av-langvarig-sykdom-form/VurderingLangvarigSykdomForm'; + +interface VurderingsdetaljvisningForEksisterendeProps { + vurderingsoversikt: Vurderingsoversikt; + vurderingselement: Vurderingselement; + editMode: boolean; + onEditClick: () => void; + onAvbrytClick: () => void; + onVurderingLagret: () => void; +} + +const getFormComponent = (vurderingstype: Vurderingstype) => { + if (vurderingstype === Vurderingstype.KONTINUERLIG_TILSYN_OG_PLEIE) { + return VurderingAvTilsynsbehovForm; + } + if (vurderingstype === Vurderingstype.TO_OMSORGSPERSONER) { + return VurderingAvToOmsorgspersonerForm; + } + if (vurderingstype === Vurderingstype.LIVETS_SLUTTFASE) { + return VurderingAvLivetsSluttfaseForm; + } + if (vurderingstype === Vurderingstype.LANGVARIG_SYKDOM) { + return VurderingLangvarigSykdomForm; + } + return null; +}; + +const getSummaryComponent = (vurderingstype: Vurderingstype) => { + if (vurderingstype === Vurderingstype.KONTINUERLIG_TILSYN_OG_PLEIE) { + return VurderingsoppsummeringForKontinuerligTilsynOgPleie; + } + if (vurderingstype === Vurderingstype.TO_OMSORGSPERSONER) { + return VurderingsoppsummeringForToOmsorgspersoner; + } + if (vurderingstype === Vurderingstype.LIVETS_SLUTTFASE) { + return VurderingsoppsummeringForSluttfase; + } + if (vurderingstype === Vurderingstype.LANGVARIG_SYKDOM) { + return VurderingsoppsummeringLangvarigSykdom; + } + return null; +}; + +const erAutomatiskVurdertInnleggelsesperiode = (vurderingselement: Vurderingselement) => + !(vurderingselement as ManuellVurdering).resultat; + +const VurderingsdetaljvisningForEksisterendeVurdering = ({ + vurderingsoversikt, + vurderingselement, + editMode, + onEditClick, + onAvbrytClick, + onVurderingLagret, +}: VurderingsdetaljvisningForEksisterendeProps): JSX.Element => { + const { endpoints } = React.useContext(ContainerContext); + const { vurderingstype } = React.useContext(VurderingContext); + + if (erAutomatiskVurdertInnleggelsesperiode(vurderingselement)) { + return ( + + ); + } + + const manuellVurdering = vurderingselement as ManuellVurdering; + const url = findHrefByRel(LinkRel.HENT_VURDERING, manuellVurdering.links); + + return ( + { + if (editMode) { + const endreLink = findLinkByRel(LinkRel.ENDRE_VURDERING, manuellVurdering.links); + const vurderingsversjon = vurdering.versjoner[0]; + + const FormComponent = getFormComponent(vurderingstype); + return ( + { + if (Vurderingstype.LIVETS_SLUTTFASE === vurderingstype) { + return ( + + ); + } + return ( + + ); + }} + vurderingsid={vurderingselement.id} + vurderingsversjonId={vurderingsversjon.versjon} + onVurderingLagret={onVurderingLagret} + /> + ); + } + + const SummaryComponent = getSummaryComponent(vurderingstype); + return ( + + ); + }} + /> + ); +}; + +export default VurderingsdetaljvisningForEksisterendeVurdering; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljvisning-for-ny-vurdering/VurderingsdetaljvisningForNyVurdering.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljvisning-for-ny-vurdering/VurderingsdetaljvisningForNyVurdering.tsx" new file mode 100644 index 0000000000..cd587872b1 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsdetaljvisning-for-ny-vurdering/VurderingsdetaljvisningForNyVurdering.tsx" @@ -0,0 +1,179 @@ +import React from 'react'; +import { addYearsToDate, Period } from '@fpsak-frontend/utils'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import { findLinkByRel } from '../../../util/linkUtils'; +import LinkRel from '../../../constants/LinkRel'; +import ContainerContext from '../../context/ContainerContext'; +import Vurderingstype from '../../../types/Vurderingstype'; +import VurderingAvTilsynsbehovForm, { + FieldName as KTPFieldName, + VurderingAvTilsynsbehovFormState, +} from '../vurdering-av-tilsynsbehov-form/VurderingAvTilsynsbehovForm'; +import VurderingAvToOmsorgspersonerForm, { + FieldName as TOFieldName, + VurderingAvToOmsorgspersonerFormState, +} from '../vurdering-av-to-omsorgspersoner-form/VurderingAvToOmsorgspersonerForm'; +import VurderingAvLivetsSluttfaseForm, { + FieldName as LivetsSluttfaseFieldName, + VurderingAvLivetsSluttfaseFormState, +} from '../vurdering-av-livets-sluttfase-form/VurderingAvLivetsSluttfaseForm'; +import NyVurderingController from '../ny-vurdering-controller/NyVurderingController'; +import VurderingContext from '../../context/VurderingContext'; +import { finnMaksavgrensningerForPerioder } from '../../../util/periodUtils'; +import VurderingLangvarigSykdomForm, { + FieldName as LangvarigSykdomFieldName, + VurderingLangvarigSykdomFormState, +} from '../vurdering-av-langvarig-sykdom-form/VurderingLangvarigSykdomForm'; + +interface VurderingsdetaljvisningForNyVurderingProps { + vurderingsoversikt: Vurderingsoversikt; + radForNyVurderingVises: boolean; + onVurderingLagret: () => void; + onAvbryt: () => void; +} + +function makeDefaultValues( + vurderingstype: Vurderingstype, + perioder: Period[], +): + | VurderingAvToOmsorgspersonerFormState + | VurderingAvTilsynsbehovFormState + | VurderingAvLivetsSluttfaseFormState + | VurderingLangvarigSykdomFormState { + if (vurderingstype === Vurderingstype.KONTINUERLIG_TILSYN_OG_PLEIE) { + return { + [KTPFieldName.VURDERING_AV_KONTINUERLIG_TILSYN_OG_PLEIE]: '', + [KTPFieldName.HAR_BEHOV_FOR_KONTINUERLIG_TILSYN_OG_PLEIE]: undefined, + [KTPFieldName.PERIODER]: perioder, + [KTPFieldName.DOKUMENTER]: [], + }; + } + if (vurderingstype === Vurderingstype.TO_OMSORGSPERSONER) { + return { + [TOFieldName.VURDERING_AV_TO_OMSORGSPERSONER]: '', + [TOFieldName.HAR_BEHOV_FOR_TO_OMSORGSPERSONER]: undefined, + [TOFieldName.PERIODER]: perioder, + [TOFieldName.DOKUMENTER]: [], + }; + } + + if (vurderingstype === Vurderingstype.LIVETS_SLUTTFASE) { + return { + [LivetsSluttfaseFieldName.VURDERING_AV_LIVETS_SLUTTFASE]: '', + [LivetsSluttfaseFieldName.ER_I_LIVETS_SLUTTFASE]: undefined, + [LivetsSluttfaseFieldName.SPLITT_PERIODE_DATO]: finnMaksavgrensningerForPerioder(perioder).fom, + [LivetsSluttfaseFieldName.DOKUMENTER]: [], + [LivetsSluttfaseFieldName.PERIODER]: perioder, + }; + } + + if (vurderingstype === Vurderingstype.LANGVARIG_SYKDOM) { + return { + [LangvarigSykdomFieldName.VURDERING_LANGVARIG_SYKDOM]: '', + [LangvarigSykdomFieldName.HAR_LANGVARIG_SYKDOM]: undefined, + [LangvarigSykdomFieldName.SPLITT_PERIODE_DATO]: finnMaksavgrensningerForPerioder(perioder).fom, + [LangvarigSykdomFieldName.DOKUMENTER]: [], + [LangvarigSykdomFieldName.PERIODER]: perioder, + }; + } + + return null; +} + +const VurderingsdetaljvisningForNyVurdering = ({ + vurderingsoversikt, + onVurderingLagret, + onAvbryt, + radForNyVurderingVises, +}: VurderingsdetaljvisningForNyVurderingProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + + const opprettLink = findLinkByRel(LinkRel.OPPRETT_VURDERING, vurderingsoversikt.links); + const resterendeVurderingsperioderDefaultValue = vurderingsoversikt.resterendeVurderingsperioder; + + const defaultPerioder = () => { + if (resterendeVurderingsperioderDefaultValue?.length > 0) { + return resterendeVurderingsperioderDefaultValue; + } + + const skalViseValgfriePerioder = !readOnly && vurderingsoversikt?.resterendeValgfrieVurderingsperioder.length > 0; + if (skalViseValgfriePerioder) { + return vurderingsoversikt.resterendeValgfrieVurderingsperioder || [new Period('', '')]; + } + + return [new Period('', '')]; + }; + + const { endpoints } = React.useContext(ContainerContext); + const { vurderingstype } = React.useContext(VurderingContext); + return ( + { + if (Vurderingstype.KONTINUERLIG_TILSYN_OG_PLEIE === vurderingstype) { + return ( + onAvbryt() : undefined} + isSubmitting={isSubmitting} + harPerioderDerPleietrengendeErOver18år={vurderingsoversikt.harPerioderDerPleietrengendeErOver18år} + barnetsAttenårsdag={ + vurderingsoversikt.harPerioderDerPleietrengendeErOver18år + ? addYearsToDate(vurderingsoversikt.pleietrengendesFødselsdato, 18) + : undefined + } + /> + ); + } + if (Vurderingstype.TO_OMSORGSPERSONER === vurderingstype) { + return ( + onAvbryt() : undefined} + isSubmitting={isSubmitting} + /> + ); + } + if (Vurderingstype.LIVETS_SLUTTFASE === vurderingstype) { + return ( + onAvbryt() : undefined} + isSubmitting={isSubmitting} + /> + ); + } + if (Vurderingstype.LANGVARIG_SYKDOM === vurderingstype) { + return ( + onAvbryt() : undefined} + isSubmitting={isSubmitting} + /> + ); + } + return null; + }} + /> + ); +}; + +export default VurderingsdetaljvisningForNyVurdering; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsnavigasjon/Vurderingsnavigasjon.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsnavigasjon/Vurderingsnavigasjon.tsx" new file mode 100644 index 0000000000..c7ea514df4 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsnavigasjon/Vurderingsnavigasjon.tsx" @@ -0,0 +1,194 @@ +import { BodyShort, Heading, Tag } from '@navikt/ds-react'; +import { + ContentWithTooltip, + EditedBySaksbehandlerIcon, + InfoIcon, + InteractiveList, + WarningIcon, +} from '@navikt/ft-plattform-komponenter'; +import { Period, sortPeriodsByFomDate } from '@fpsak-frontend/utils'; +import React, { useEffect } from 'react'; +import ManuellVurdering from '../../../types/ManuellVurdering'; +import Vurderingselement from '../../../types/Vurderingselement'; +import usePrevious from '../../../util/hooks'; +import AddButton from '../add-button/AddButton'; +import VurderingsperiodeElement from '../vurderingsperiode/VurderingsperiodeElement'; +import Vurderingsperioder from '../vurderingsperioder/Vurderingsperioder'; +import WriteAccessBoundContent from '../write-access-bound-content/WriteAccessBoundContent'; +import styles from './vurderingsnavigasjon.css'; + +interface VurderingsnavigasjonProps { + vurderingselementer: Vurderingselement[]; + onNyVurderingClick: (perioder?: Period[]) => void; + onVurderingValgt: (vurdering: Vurderingselement) => void; + resterendeVurderingsperioder?: Period[]; + visRadForNyVurdering?: boolean; + visParterLabel?: boolean; + visOpprettVurderingKnapp?: boolean; + resterendeValgfrieVurderingsperioder?: Period[]; +} + +const Vurderingsnavigasjon = ({ + vurderingselementer, + onNyVurderingClick, + onVurderingValgt, + resterendeVurderingsperioder, + visRadForNyVurdering, + visParterLabel, + visOpprettVurderingKnapp, + resterendeValgfrieVurderingsperioder, +}: VurderingsnavigasjonProps): JSX.Element => { + const harPerioderSomSkalVurderes = resterendeVurderingsperioder?.length > 0; + const [activeIndex, setActiveIndex] = React.useState(harPerioderSomSkalVurderes ? 0 : -1); + const previousVisRadForNyVurdering = usePrevious(visRadForNyVurdering); + const harValgfrieVurderingsperioder = resterendeValgfrieVurderingsperioder?.length > 0; + + useEffect(() => { + if (visRadForNyVurdering === false && previousVisRadForNyVurdering === true) { + setActiveIndex(-1); + } + }, [visRadForNyVurdering]); + + const sorterteVurderingselementer: Vurderingselement[] = React.useMemo( + () => vurderingselementer.sort((p1, p2) => sortPeriodsByFomDate(p1.periode, p2.periode)).reverse(), + [vurderingselementer], + ); + + const vurderingsperiodeElements = sorterteVurderingselementer.map(vurderingsperiode => { + const visOverlappetikett = + harPerioderSomSkalVurderes && + resterendeVurderingsperioder.some((søknadsperiode: Period) => + søknadsperiode.overlapsWith(vurderingsperiode.periode), + ); + + return ( + ( +
    + {(vurderingsperiode as ManuellVurdering).endretIDenneBehandlingen && ( + + + + )} + + {visOverlappetikett && ( + + Overlapp + + )} +
    + )} + /> + ); + }); + + const allElements = [...vurderingsperiodeElements]; + if (harPerioderSomSkalVurderes) { + allElements.unshift( + ( + + + + )} + visParterLabel={visParterLabel} + perioder={resterendeVurderingsperioder || []} + />, + ); + } else if (harValgfrieVurderingsperioder) { + allElements.unshift( + ( + + + + )} + visParterLabel={visParterLabel} + perioder={resterendeValgfrieVurderingsperioder || []} + />, + ); + } + + if (visRadForNyVurdering) { + allElements.unshift( + + Ny vurdering + , + ); + } + + return ( +
    +
    + + Alle perioder + + {visOpprettVurderingKnapp && ( + ( + { + setActiveIndex(0); + onNyVurderingClick(); + }} + ariaLabel="Opprett vurdering" + /> + )} + /> + )} +
    + {allElements.length === 0 && ( +

    Ingen vurderinger å vise

    + )} + {allElements.length > 0 && ( +
    +
    +
    + + Status + +
    +
    + + Periode + +
    + {visParterLabel && ( +
    + + Part + +
    + )} +
    + ({ + content: element, + active: activeIndex === currentIndex, + key: `${currentIndex}`, + onClick: () => { + setActiveIndex(currentIndex); + + const vurderingsperiodeIndex = vurderingsperiodeElements.indexOf(element); + const erEnEksisterendeVurdering = vurderingsperiodeIndex > -1; + if (erEnEksisterendeVurdering) { + onVurderingValgt(sorterteVurderingselementer[vurderingsperiodeIndex]); + } else if (visRadForNyVurdering && currentIndex === 0) { + onNyVurderingClick(); + } else { + onNyVurderingClick(resterendeVurderingsperioder); + } + }, + }))} + /> +
    + )} +
    + ); +}; + +export default Vurderingsnavigasjon; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsnavigasjon/vurderingsnavigasjon.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsnavigasjon/vurderingsnavigasjon.css" new file mode 100644 index 0000000000..e462746c20 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsnavigasjon/vurderingsnavigasjon.css" @@ -0,0 +1,57 @@ +.nyVurderingKnapp { + flex-basis: 40%; + margin-left: auto; +} + +.vurderingsvelgerContainer { + flex-grow: 1; + margin-top: 0.125rem; + padding: 1.25rem 0 0; +} + +.vurderingsvelgerContainer__columnHeadings { + display: flex; + margin-bottom: 0.25rem; +} + +.vurderingsvelgerContainer__columnHeading--first { + width: 4.1875rem; + margin-left: 0.875rem; +} + +.vurderingsvelgerContainer__columnHeading--third { + margin-left: 8.1875rem; +} + +.vurderingsperiode__postElementContainer { + margin-left: 1.375rem; + display: flex; +} + +.vurderingsperiode__postElementContainer :nth-child(2) { + margin-left: 1rem; +} + +.vurderingsperiode__postElementContainer :global .editedBySaksbehandlerIcon { + position: absolute; + height: 1.875rem; +} + +button.vurderingsnavigasjon__opprettVurderingKnapp { + margin-top: 1.375rem; + padding-right: 1.75rem; + font-size: 0.875rem; +} + +.vurderingsnavigasjon__headingContainer { + display: flex; + justify-content: space-between; +} + +.vurderingsnavigasjon__heading { + padding: 1.25rem 0.9375rem 0; +} + +.vurderingsnavigasjon__ingenVurderinger { + margin-left: 0.9375rem; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-innleggelsesperiode/VurderingsoppsummeringForInnleggelsesperiode.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-innleggelsesperiode/VurderingsoppsummeringForInnleggelsesperiode.tsx" new file mode 100644 index 0000000000..0ba92d9904 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-innleggelsesperiode/VurderingsoppsummeringForInnleggelsesperiode.tsx" @@ -0,0 +1,30 @@ +import React from 'react'; +import { Alert } from '@navikt/ds-react'; +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import InnleggelsesperiodeVurdering from '../../../types/InnleggelsesperiodeVurdering'; +import Vurderingstype from '../../../types/Vurderingstype'; +import DetailViewVurdering from '../detail-view-vurdering/DetailViewVurdering'; + +interface VurderingsoppsummeringForInnleggelsesperiodeProps { + vurdering: InnleggelsesperiodeVurdering; + vurderingstype: Vurderingstype; +} + +const VurderingsoppsummeringForInnleggelsesperiode = ({ + vurdering, + vurderingstype, +}: VurderingsoppsummeringForInnleggelsesperiodeProps): JSX.Element => { + const vurderingstekst = + vurderingstype === Vurderingstype.TO_OMSORGSPERSONER ? 'to omsorgspersoner' : 'tilsyn og pleie'; + return ( + + + + Innvilget som følge av innleggelse + + + + ); +}; + +export default VurderingsoppsummeringForInnleggelsesperiode; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-kontinuerlig-tilsyn-og-pleie/VurderingsoppsummeringForKontinuerligTilsynOgPleie.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-kontinuerlig-tilsyn-og-pleie/VurderingsoppsummeringForKontinuerligTilsynOgPleie.tsx" new file mode 100644 index 0000000000..9ccd700ece --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-kontinuerlig-tilsyn-og-pleie/VurderingsoppsummeringForKontinuerligTilsynOgPleie.tsx" @@ -0,0 +1,83 @@ +import React from 'react'; +import { Box, Margin, BasicList, LabelledContent, AssessedBy } from '@navikt/ft-plattform-komponenter'; +import Vurdering from '../../../types/Vurdering'; +import DokumentLink from '../dokument-link/DokumentLink'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import DekketAvInnleggelsesperiodeMelding from '../dekket-av-innleggelsesperiode-melding/DekketAvInnleggelsesperiodeMelding'; +import DetailViewVurdering from '../detail-view-vurdering/DetailViewVurdering'; +import ContainerContext from '../../context/ContainerContext'; + +interface VurderingsoppsummeringForKontinuerligTilsynOgPleieProps { + vurdering: Vurdering; + redigerVurdering: () => void; + erInnleggelsesperiode: boolean; +} + +const VurderingsoppsummeringForKontinuerligTilsynOgPleie = ({ + vurdering, + redigerVurdering, + erInnleggelsesperiode, +}: VurderingsoppsummeringForKontinuerligTilsynOgPleieProps): JSX.Element => { + const gjeldendeVurdering = vurdering.versjoner[0]; + const { dokumenter, perioder, tekst, resultat } = gjeldendeVurdering; + const brukerId = gjeldendeVurdering.endretAv; + const { saksbehandlere } = React.useContext(ContainerContext); + + return ( + + + {erInnleggelsesperiode && } + + + benyttet) + .map(dokument => ( + + ))} + /> + + } + /> + + + {tekst}} + indentContent + /> + + + + {resultat === Vurderingsresultat.OPPFYLT ? 'Ja' : 'Nei'}} + /> + + + + {perioder.map(period => { + const prettyPeriod = period.prettifyPeriod(); + return
  • {prettyPeriod}
  • ; + })} + + } + /> +
    + +
    + ); +}; + +export default VurderingsoppsummeringForKontinuerligTilsynOgPleie; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-kontinuerlig-tilsyn-og-pleie/vurderingsoppsummeringForKontinuerligTilsynOgPleie.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-kontinuerlig-tilsyn-og-pleie/vurderingsoppsummeringForKontinuerligTilsynOgPleie.css" new file mode 100644 index 0000000000..9b3bb25d0f --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-kontinuerlig-tilsyn-og-pleie/vurderingsoppsummeringForKontinuerligTilsynOgPleie.css" @@ -0,0 +1,8 @@ +.vurderingsdetaljer { + padding: 1rem; + background: rgba(204, 225, 243, 0.26); +} + +.vurderingsdetaljer .begrunnelse { + margin: 0; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-langvarig-sykdom/VurderingsoppsummeringLangvarigSykdom.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-langvarig-sykdom/VurderingsoppsummeringLangvarigSykdom.tsx" new file mode 100644 index 0000000000..af994e10f9 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-langvarig-sykdom/VurderingsoppsummeringLangvarigSykdom.tsx" @@ -0,0 +1,62 @@ +import React from 'react'; +import { Box, Margin, BasicList, LabelledContent, AssessedBy } from '@navikt/ft-plattform-komponenter'; +import Vurdering from '../../../types/Vurdering'; +import DokumentLink from '../dokument-link/DokumentLink'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import DetailViewVurdering from '../detail-view-vurdering/DetailViewVurdering'; +import ContainerContext from '../../context/ContainerContext'; + +interface VurderingsoppsummeringLangvarigSykdom { + vurdering: Vurdering; + redigerVurdering: () => void; +} + +const VurderingsoppsummeringLangvarigSykdom = ({ + vurdering, + redigerVurdering, +}: VurderingsoppsummeringLangvarigSykdom): JSX.Element => { + const gjeldendeVurdering = vurdering.versjoner[0]; + const { dokumenter, perioder, tekst, resultat } = gjeldendeVurdering; + const brukerId = gjeldendeVurdering.endretAv; + const { saksbehandlere } = React.useContext(ContainerContext); + + return ( + + + + + benyttet) + .map(dokument => ( + + ))} + /> + + } + /> + + + {tekst}} + indentContent + /> + + + + {resultat === Vurderingsresultat.OPPFYLT ? 'Ja' : 'Nei'}} + /> + + + + ); +}; + +export default VurderingsoppsummeringLangvarigSykdom; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-livets-sluttfase/VurderingsoppsummeringForSluttfase.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-livets-sluttfase/VurderingsoppsummeringForSluttfase.tsx" new file mode 100644 index 0000000000..71f7b07720 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-livets-sluttfase/VurderingsoppsummeringForSluttfase.tsx" @@ -0,0 +1,69 @@ +import React from 'react'; +import { Box, Margin, BasicList, LabelledContent, AssessedBy } from '@navikt/ft-plattform-komponenter'; +import Vurdering from '../../../types/Vurdering'; +import DokumentLink from '../dokument-link/DokumentLink'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import DekketAvInnleggelsesperiodeMelding from '../dekket-av-innleggelsesperiode-melding/DekketAvInnleggelsesperiodeMelding'; +import DetailViewVurdering from '../detail-view-vurdering/DetailViewVurdering'; +import ContainerContext from '../../context/ContainerContext'; + +interface VurderingsoppsummeringForSluttfaseProps { + vurdering: Vurdering; + redigerVurdering: () => void; + erInnleggelsesperiode: boolean; +} + +const VurderingsoppsummeringForSluttfase = ({ + vurdering, + redigerVurdering, + erInnleggelsesperiode, +}: VurderingsoppsummeringForSluttfaseProps): JSX.Element => { + const gjeldendeVurdering = vurdering.versjoner[0]; + const { dokumenter, perioder, tekst, resultat } = gjeldendeVurdering; + const brukerId = gjeldendeVurdering.endretAv; + const { saksbehandlere } = React.useContext(ContainerContext); + + return ( + + + {erInnleggelsesperiode && } + + + benyttet) + .map(dokument => ( + + ))} + /> + + } + /> + + + {resultat === Vurderingsresultat.OPPFYLT ? 'Ja' : 'Nei'}} + /> + + + {tekst}} + indentContent + /> + + + + + ); +}; + +export default VurderingsoppsummeringForSluttfase; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-to-omsorgspersoner/VurderingsoppsummeringForToOmsorgspersoner.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-to-omsorgspersoner/VurderingsoppsummeringForToOmsorgspersoner.tsx" new file mode 100644 index 0000000000..c099a81175 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-to-omsorgspersoner/VurderingsoppsummeringForToOmsorgspersoner.tsx" @@ -0,0 +1,80 @@ +import React from 'react'; +import { Box, Margin, BasicList, LabelledContent, AssessedBy } from '@navikt/ft-plattform-komponenter'; +import ContainerContext from '../../context/ContainerContext'; +import Vurdering from '../../../types/Vurdering'; +import DokumentLink from '../dokument-link/DokumentLink'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import DekketAvInnleggelsesperiodeMelding from '../dekket-av-innleggelsesperiode-melding/DekketAvInnleggelsesperiodeMelding'; +import DetailViewVurdering from '../detail-view-vurdering/DetailViewVurdering'; + +interface VurderingsoppsummeringForToOmsorgspersonerProps { + vurdering: Vurdering; + redigerVurdering: () => void; + erInnleggelsesperiode: boolean; +} + +const VurderingsoppsummeringForToOmsorgspersoner = ({ + vurdering, + redigerVurdering, + erInnleggelsesperiode, +}: VurderingsoppsummeringForToOmsorgspersonerProps): JSX.Element => { + const gjeldendeVurdering = vurdering.versjoner[0]; + const { resultat, tekst, dokumenter, perioder } = gjeldendeVurdering; + const brukerId = gjeldendeVurdering.endretAv; + const { saksbehandlere } = React.useContext(ContainerContext); + + return ( + + + {erInnleggelsesperiode && } + + benyttet) + .map(dokument => ( + + ))} + /> + } + /> + + + {tekst}} + indentContent + /> + + + + {resultat === Vurderingsresultat.OPPFYLT ? 'Ja' : 'Nei'}} + /> + + + + {perioder.map(period => { + const prettyPeriod = period.prettifyPeriod(); + return
  • {prettyPeriod}
  • ; + })} + + } + /> +
    +
    +
    + ); +}; + +export default VurderingsoppsummeringForToOmsorgspersoner; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-to-omsorgspersoner/vurderingsoppsummeringForToOmsorgspersoner.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-to-omsorgspersoner/vurderingsoppsummeringForToOmsorgspersoner.css" new file mode 100644 index 0000000000..9b3bb25d0f --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoppsummering-for-to-omsorgspersoner/vurderingsoppsummeringForToOmsorgspersoner.css" @@ -0,0 +1,8 @@ +.vurderingsdetaljer { + padding: 1rem; + background: rgba(204, 225, 243, 0.26); +} + +.vurderingsdetaljer .begrunnelse { + margin: 0; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoversikt-langvarig-sykdom-messages/VurderingsoversiktLangvarigSykdomMessages.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoversikt-langvarig-sykdom-messages/VurderingsoversiktLangvarigSykdomMessages.tsx" new file mode 100644 index 0000000000..2a1e31b692 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoversikt-langvarig-sykdom-messages/VurderingsoversiktLangvarigSykdomMessages.tsx" @@ -0,0 +1,37 @@ +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import { getHumanReadablePeriodString } from '@fpsak-frontend/utils'; +import { Alert } from '@navikt/ds-react'; +import React from 'react'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import IngenPerioderÅVurdereMelding from '../ingen-perioder-å-vurdere-melding/IngenPerioderÅVurdereMelding'; + +interface VurderingsoversiktLangvarigSykdomMessagesProps { + vurderingsoversikt: Vurderingsoversikt; +} + +const VurderingsoversiktLangvarigSykdomMessages = ({ + vurderingsoversikt, +}: VurderingsoversiktLangvarigSykdomMessagesProps): JSX.Element => { + if (vurderingsoversikt.harIngenPerioderÅVise()) { + return ( + + + + ); + } + + if (vurderingsoversikt.harPerioderSomSkalVurderes() === true) { + return ( + + + {`Vurder om pleietrengende har langvarig sykdom i søknadsperioden ${getHumanReadablePeriodString( + vurderingsoversikt.resterendeVurderingsperioder, + )}.`} + + + ); + } + return null; +}; + +export default VurderingsoversiktLangvarigSykdomMessages; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoversikt-messages/VurderingsoversiktMessages.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoversikt-messages/VurderingsoversiktMessages.tsx" new file mode 100644 index 0000000000..e0865cdb93 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoversikt-messages/VurderingsoversiktMessages.tsx" @@ -0,0 +1,83 @@ +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import { addYearsToDate, getHumanReadablePeriodString } from '@fpsak-frontend/utils'; +import { Alert } from '@navikt/ds-react'; +import React from 'react'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import Vurderingstype from '../../../types/Vurderingstype'; +import IngenPerioderÅVurdereMelding from '../ingen-perioder-å-vurdere-melding/IngenPerioderÅVurdereMelding'; +import ManglerGyldigSignaturMelding from '../mangler-gyldig-signatur-melding/ManglerGyldigSignaturMelding'; +import VurderingContext from '../../context/VurderingContext'; + +interface VurderingsoversiktMessagesProps { + vurderingsoversikt: Vurderingsoversikt; + harGyldigSignatur: boolean; +} + +const VurderingsoversiktMessages = ({ + vurderingsoversikt, + harGyldigSignatur, +}: VurderingsoversiktMessagesProps): JSX.Element => { + const { vurderingstype } = React.useContext(VurderingContext); + const vurderingsnavn = + vurderingstype === Vurderingstype.TO_OMSORGSPERSONER ? 'to omsorgspersoner' : 'tilsyn og pleie'; + + if (!harGyldigSignatur) { + return ( + + + Du kan ikke vurdere behov for + {` ${vurderingsnavn} `} + før søker har sendt inn legeerklæring fra sykehus/spesialisthelsetjenesten. + + + ); + } + + if (vurderingsoversikt.harIngenPerioderÅVise()) { + return ( + + + + ); + } + + if (vurderingsoversikt.harPerioderSomSkalVurderes() === true) { + const barnetsAttenårsdag = vurderingsoversikt.harPerioderDerPleietrengendeErOver18år + ? addYearsToDate(vurderingsoversikt.pleietrengendesFødselsdato, 18) + : null; + + return ( + <> + + + {`Vurder behov for ${vurderingsnavn} for ${getHumanReadablePeriodString( + vurderingsoversikt.resterendeVurderingsperioder, + )}.`} + + + {vurderingsoversikt.harPerioderDerPleietrengendeErOver18år && ( + + + Barnet er 18 år {barnetsAttenårsdag}. Du må gjøre en egen vurdering etter § 9-10, tredje ledd fra datoen + barnet fyller 18 år. + + + )} + + ); + /* + Please note: + So long as this doesnt actually do anything upon the click-event, it should be commented out. + overlappendeVurderingsperioder && overlappendeVurderingsperioder.length > 0 && ( + + console.log('does something')} + overlappendeVurderingsperioder={overlappendeVurderingsperioder} + /> + ) + */ + } + return null; +}; + +export default VurderingsoversiktMessages; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoversikt-sluttfase-messages/VurderingsoversiktSluttfaseMessages.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoversikt-sluttfase-messages/VurderingsoversiktSluttfaseMessages.tsx" new file mode 100644 index 0000000000..c2c031c4e6 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsoversikt-sluttfase-messages/VurderingsoversiktSluttfaseMessages.tsx" @@ -0,0 +1,51 @@ +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import { getHumanReadablePeriodString } from '@fpsak-frontend/utils'; +import { Alert } from '@navikt/ds-react'; +import React from 'react'; +import Vurderingsoversikt from '../../../types/Vurderingsoversikt'; +import IngenPerioderÅVurdereMelding from '../ingen-perioder-å-vurdere-melding/IngenPerioderÅVurdereMelding'; +import ManglerGyldigSignaturMelding from '../mangler-gyldig-signatur-melding/ManglerGyldigSignaturMelding'; + +interface VurderingsoversiktSluttfaseMessagesProps { + vurderingsoversikt: Vurderingsoversikt; + harGyldigSignatur: boolean; +} + +const VurderingsoversiktSluttfaseMessages = ({ + vurderingsoversikt, + harGyldigSignatur, +}: VurderingsoversiktSluttfaseMessagesProps): JSX.Element => { + if (!harGyldigSignatur) { + return ( + + + Du kan ikke vurdere behov for om pleietrengende er i livets sluttfase før søker har sendt inn legeerklæring + fra lege eller helseinstitusjon. + + + ); + } + + if (vurderingsoversikt.harIngenPerioderÅVise()) { + return ( + + + + ); + } + + if (vurderingsoversikt.harPerioderSomSkalVurderes() === true) { + return ( + + + {`Vurder om pleietrengende er i livets sluttfase i søknadsperioden ${getHumanReadablePeriodString( + vurderingsoversikt.resterendeVurderingsperioder, + )}.`} + + + ); + } + return null; +}; + +export default VurderingsoversiktSluttfaseMessages; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperiode/VurderingsperiodeElement.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperiode/VurderingsperiodeElement.tsx" new file mode 100644 index 0000000000..b61050481c --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperiode/VurderingsperiodeElement.tsx" @@ -0,0 +1,123 @@ +import { + ContentWithTooltip, + GreenCheckIconFilled, + OnePersonIconGray, + OnePersonOutlineGray, + RedCrossIconFilled, + TwoPersonsWithOneHighlightedIconGray, + InstitutionIcon, +} from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import ManuellVurdering from '../../../types/ManuellVurdering'; +import InnleggelsesperiodeIkonOverOppfylt from '../innleggelsesperiode-ikon-over-oppfylt/InnleggelsesperiodeIkonOverOppfylt'; +import InnleggelsesperiodeIkonOverIkkeOppfylt from '../innleggelsesperiode-ikon-over-ikkeoppfylt/InnleggelsesperiodeIkonOverIkkeOppfylt'; +import Vurderingselement from '../../../types/Vurderingselement'; +import styles from './vurderingsperiodeElement.css'; + +interface VurderingsperiodeElementProps { + vurderingselement: Vurderingselement; + renderAfterElement?: () => React.ReactNode; + visParterLabel?: boolean; +} + +const renderInnleggelsesperiodeIcon = (resultat: Vurderingsresultat) => { + if (resultat === Vurderingsresultat.OPPFYLT) { + return ( + + + + ); + } + if (resultat === Vurderingsresultat.IKKE_OPPFYLT) { + return ( + + + + ); + } + return ( + + + + ); +}; + +const renderResultatIcon = (resultat: Vurderingsresultat) => { + if (resultat === Vurderingsresultat.OPPFYLT) { + return ( + + + + ); + } + if (resultat === Vurderingsresultat.IKKE_OPPFYLT) { + return ( + + + + ); + } + return null; +}; + +const renderStatusIndicator = (vurderingselement: Vurderingselement) => { + const { erInnleggelsesperiode, resultat } = vurderingselement as ManuellVurdering; + if (erInnleggelsesperiode) { + return renderInnleggelsesperiodeIcon(resultat); + } + return renderResultatIcon(resultat); +}; + +const renderPersonIcon = ({ gjelderForAnnenPart, gjelderForSøker }: ManuellVurdering) => { + if (gjelderForAnnenPart && gjelderForSøker) { + return ( + +
    + +
    +
    + ); + } + if (gjelderForAnnenPart) { + return ( + + + + ); + } + return ( + + + + ); +}; + +const VurderingsperiodeElement = ({ + vurderingselement, + renderAfterElement, + visParterLabel, +}: VurderingsperiodeElementProps): JSX.Element => { + const { periode } = vurderingselement; + return ( +
    + Type +
    {renderStatusIndicator(vurderingselement)}
    +
    +

    + Periode + {periode.prettifyPeriod()} +

    + {visParterLabel && ( +
    + Parter + {renderPersonIcon(vurderingselement as ManuellVurdering)} +
    + )} +
    + {renderAfterElement && renderAfterElement()} +
    + ); +}; + +export default VurderingsperiodeElement; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperiode/vurderingsperiodeElement.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperiode/vurderingsperiodeElement.css" new file mode 100644 index 0000000000..5467c7d4dc --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperiode/vurderingsperiodeElement.css" @@ -0,0 +1,48 @@ +.vurderingsperiodeElement { + display: flex; + cursor: pointer; +} + +.vurderingsperiodeElement__indicator { + width: 3.5rem; +} + +.vurderingsperiodeElement__texts { + display: flex; + align-items: center; +} + +.vurderingsperiodeElement__texts__period { + display: inline-block; + margin: 0; + color: #0067c5; + text-decoration: underline; + min-width: 10.0625rem; +} + +.vurderingsperiodeElement__texts__resultat { + display: inline-block; + margin: 0 0 0 1.375rem; +} + +.vurderingsperiodeElement__texts__parterIcon { + display: inline-block; + vertical-align: top; + margin-top: -0.125rem; + margin-left: 1.625rem; +} + +.vurderingsperiodeElement__texts__parterIcon--wide svg { + width: 2.25rem; +} + +.vurderingsperiodeElement .visuallyHidden { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperioder/Vurderingsperioder.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperioder/Vurderingsperioder.tsx" new file mode 100644 index 0000000000..26d826ee13 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperioder/Vurderingsperioder.tsx" @@ -0,0 +1,46 @@ +import { ContentWithTooltip, OnePersonIconGray } from '@navikt/ft-plattform-komponenter'; +import { Period } from '@fpsak-frontend/utils'; +import React from 'react'; +import styles from './vurderingsperioder.css'; + +interface VurderingsperioderProps { + perioder: Period[]; + visParterLabel?: boolean; + indicatorContentRenderer?: () => React.ReactNode; +} + +const Vurderingsperioder = ({ + perioder, + visParterLabel, + indicatorContentRenderer, +}: VurderingsperioderProps): JSX.Element => ( +
    + {indicatorContentRenderer && ( + <> + Type +
    {indicatorContentRenderer()}
    + + )} + +
    +
    + {perioder.map((periode, index) => ( +

    + {index === 0 && Perioder} + {periode.prettifyPeriod()} +

    + ))} +
    + {visParterLabel && ( +
    + Parter + + + +
    + )} +
    +
    +); + +export default Vurderingsperioder; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperioder/vurderingsperioder.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperioder/vurderingsperioder.css" new file mode 100644 index 0000000000..51d37bceb8 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/vurderingsperioder/vurderingsperioder.css" @@ -0,0 +1,42 @@ +.vurderingsperioder { + display: flex; + align-items: center; + cursor: pointer; +} + +.vurderingsperioder__indicator { + width: 3.5rem; +} + +.vurderingsperioder__texts { + display: flex; + align-items: center; +} + +.vurderingsperioder__texts__period { + margin: 0; + color: #0067c5; + text-decoration: underline; + min-width: 10.0625rem; +} + +.vurderingsperioder__texts__parterIcon { + display: inline-block; + vertical-align: top; + margin-left: 1.625rem; +} + +.vurderingsperioder__etikett { + margin-left: 1.375rem; +} + +.vurderingsperioder .visuallyHidden { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx" new file mode 100644 index 0000000000..05fe3c3eaf --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx" @@ -0,0 +1,20 @@ +import React from 'react'; +import ContainerContext from '../../context/ContainerContext'; + +interface WriteAccessBoundContentProps { + contentRenderer: () => JSX.Element; + otherRequirementsAreMet?: boolean; +} + +const WriteAccessBoundContent = ({ + contentRenderer, + otherRequirementsAreMet, +}: WriteAccessBoundContentProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + if (readOnly === false && (otherRequirementsAreMet === true || otherRequirementsAreMet === undefined)) { + return contentRenderer(); + } + return null; +}; + +export default WriteAccessBoundContent; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/write-access-bound-content/tests/WriteAccessBoundContent.spec.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/write-access-bound-content/tests/WriteAccessBoundContent.spec.tsx" new file mode 100644 index 0000000000..3ddf938ca8 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/components/write-access-bound-content/tests/WriteAccessBoundContent.spec.tsx" @@ -0,0 +1,39 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import ContainerContext from '../../../context/ContainerContext'; +import WriteAccessBoundContent from '../WriteAccessBoundContent'; +import ContainerContract from '../../../../types/ContainerContract'; + +const testContent = 'Dette er en test'; + +const writeAccessBoundContentRenderer = (readOnly: boolean, otherRequirementsAreMet = true) => + render( + + {testContent}} + otherRequirementsAreMet={otherRequirementsAreMet} + /> + , + ); + +describe('WriteAccessBoundContent', () => { + it('should show content if readOnly is false', () => { + writeAccessBoundContentRenderer(false); + expect(screen.getByText(testContent)).toBeInTheDocument(); + }); + + it('should show content if readOnly is false and otherRequirementsAreMet is true', () => { + writeAccessBoundContentRenderer(false, true); + expect(screen.getByText(testContent)).toBeInTheDocument(); + }); + + it('should hide content if readOnly is true', () => { + writeAccessBoundContentRenderer(true); + expect(screen.queryByText(testContent)).toBeNull(); + }); + + it('should hide content if readOnly is false while otherRequirementsAreMet are false', () => { + writeAccessBoundContentRenderer(true, false); + expect(screen.queryByText(testContent)).toBeNull(); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/ContainerContext.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/ContainerContext.ts" new file mode 100644 index 0000000000..b4cd425e6a --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/ContainerContext.ts" @@ -0,0 +1,5 @@ +import React from 'react'; +import ContainerContract from '../../types/ContainerContract'; + +const ContainerContext = React.createContext(null); +export default ContainerContext; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/DiagnosekodeContext.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/DiagnosekodeContext.ts" new file mode 100644 index 0000000000..517265ca82 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/DiagnosekodeContext.ts" @@ -0,0 +1,5 @@ +import React from 'react'; +import { DiagnosekodeWrapper } from '../../types/Diagnosekode'; + +const DiagnosekodeContext = React.createContext<(diagnosekodeState: DiagnosekodeWrapper) => void | null>(null); +export default DiagnosekodeContext; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/VurderingContext.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/VurderingContext.ts" new file mode 100644 index 0000000000..7381dd6075 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/VurderingContext.ts" @@ -0,0 +1,5 @@ +import React from 'react'; +import VurderingContextType from '../../types/VurderingContext'; + +const VurderingContext = React.createContext(null); +export default VurderingContext; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/queryClient.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/queryClient.ts" new file mode 100644 index 0000000000..1088dcd1b3 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/context/queryClient.ts" @@ -0,0 +1,9 @@ +import { QueryClient } from 'react-query'; + +const queryClient = new QueryClient(); +queryClient.setQueryDefaults('diagnosekodeResponse', { + placeholderData: { diagnosekoder: [], links: [], behandlingUuid: '', versjon: null }, + staleTime: 10000, +}); + +export default queryClient; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/pure/PureDiagnosekodeSelector.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/pure/PureDiagnosekodeSelector.tsx" new file mode 100644 index 0000000000..31df09c99b --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/pure/PureDiagnosekodeSelector.tsx" @@ -0,0 +1,124 @@ +import { BodyShort, Label } from '@navikt/ds-react'; +import { Autocomplete, FieldError } from '@navikt/ft-plattform-komponenter'; +import * as React from 'react'; +import Diagnosekode from '../../../types/Diagnosekode'; +import DeleteButton from '../../components/delete-button/DeleteButton'; +import styles from './diagnosekodeSelector.css'; +import { type DiagnosekodeSearcherPromise, toLegacyDiagnosekode } from '../../../util/diagnosekodeSearcher'; + +interface Suggestion { + key: string; + value: string; +} + +interface DiagnosekodeSelectorProps { + label: string; + onChange: (value) => void; + name: string; + errorMessage?: string; + initialDiagnosekodeValue: string; + hideLabel?: boolean; + selectedDiagnosekoder: string[]; + searcherPromise: DiagnosekodeSearcherPromise; +} + +const PureDiagnosekodeSelector = ({ + label, + onChange, + name, + errorMessage, + initialDiagnosekodeValue, + hideLabel, + selectedDiagnosekoder, + searcherPromise, +}: DiagnosekodeSelectorProps): JSX.Element => { + const [suggestions, setSuggestions] = React.useState([]); + const [inputValue, setInputValue] = React.useState(''); + const [isLoading, setIsLoading] = React.useState(false); + const [selectedDiagnosekoderFullName, setSelectedDiagnosekoderFullName] = React.useState([]); + + const getUpdatedSuggestions = async (queryString: string) => { + if (queryString.length >= 3) { + setIsLoading(true); + const searcher = await searcherPromise; + const diagnosekoder: Diagnosekode[] = (await searcher.search(queryString, 1)).diagnosekoder.map( + toLegacyDiagnosekode, + ); + setIsLoading(false); + return diagnosekoder.map(({ kode, beskrivelse }) => ({ + key: kode, + value: `${kode} - ${beskrivelse}`, + })); + } + return []; + }; + + React.useEffect(() => { + const getInitialDiagnosekode = async () => { + const diagnosekode = await getUpdatedSuggestions(initialDiagnosekodeValue); + if (diagnosekode.length > 0 && diagnosekode[0].value) { + setInputValue(diagnosekode[0].value); + } + }; + getInitialDiagnosekode(); + }, [initialDiagnosekodeValue]); + + const onInputValueChange = async v => { + const newSuggestionList = await getUpdatedSuggestions(v); + setSuggestions(newSuggestionList); + }; + + const removeDiagnosekode = (diagnosekodeToRemove: string) => { + onChange(selectedDiagnosekoder.filter(selectedDiagnosekode => selectedDiagnosekode !== diagnosekodeToRemove)); + }; + return ( +
    +
    + +
    +
    + { + onInputValueChange(e); + setInputValue(e); + }} + onSelect={e => { + setInputValue(''); + if (!selectedDiagnosekoder.includes(e.key)) { + onChange([...selectedDiagnosekoder, e.key]); + setSelectedDiagnosekoderFullName([...selectedDiagnosekoderFullName, e]); + } + }} + ariaLabel="Søk etter diagnose" + placeholder="Søk etter diagnose" + shouldFocusOnMount + isLoading={isLoading} + /> +
    + {errorMessage && } + {selectedDiagnosekoder.length > 0 && ( +
      + {selectedDiagnosekoder.map(selectedDiagnosekode => { + const fullName = selectedDiagnosekoderFullName.find( + selectedDiagnosekodeFullName => selectedDiagnosekodeFullName.key === selectedDiagnosekode, + ); + const diagnosekodeLabel = fullName ? fullName.value : selectedDiagnosekode; + return ( +
    • + {diagnosekodeLabel} + removeDiagnosekode(selectedDiagnosekode)} /> +
    • + ); + })} +
    + )} +
    + ); +}; + +export default PureDiagnosekodeSelector; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/pure/diagnosekodeSelector.css" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/pure/diagnosekodeSelector.css" new file mode 100644 index 0000000000..bbe4576820 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/pure/diagnosekodeSelector.css" @@ -0,0 +1,60 @@ +.diagnosekodeContainer { + width: 100%; +} + +.diagnosekodeContainer input { + min-width: 500px; + width: 40%; +} + +.diagnosekodeContainer__hideLabel { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} + +.diagnosekodeContainer__autocompleteContainer { + display: flex; + align-items: center; +} + +.diagnosekodeContainer__spinnerContainer { + margin-left: 1rem; + width: 2rem; +} + +.diagnosekodeContainer__diagnosekodeList { + margin: 1rem 0 0; + padding: 0; +} + +.diagnosekodeContainer__diagnosekodeList li { + align-items: center; + display: flex; + justify-content: space-between; + list-style-type: none; + padding: 0.75rem 0; +} + +.diagnosekodeContainer__diagnosekodeList li:not(:first-of-type) { + border-top: 1px solid #c6c2bf; +} + +.diagnosekodeContainer__diagnosekodeList li:last-of-type { + padding-bottom: 0; +} + +.diagnosekodeContainer__diagnosekodeList li > div { + margin: 0; +} + +.diagnosekodeContainer__diagnosekodeList :global(.navds-body-short) { + text-overflow: ellipsis; + white-space: nowrap; + max-width: 500px; + overflow: hidden; +} diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/types/PeriodWrapper.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/types/PeriodWrapper.ts" new file mode 100644 index 0000000000..a189a41e77 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/types/PeriodWrapper.ts" @@ -0,0 +1,7 @@ +import { Period } from '@fpsak-frontend/utils'; + +interface PeriodWrapper { + period: Period; +} + +export default PeriodWrapper; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/validators/__tests__/validators.spec.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/validators/__tests__/validators.spec.ts" new file mode 100644 index 0000000000..5642764353 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/validators/__tests__/validators.spec.ts" @@ -0,0 +1,72 @@ +import { dateConstants, Period } from '@fpsak-frontend/utils'; +import { + dateIsNotInTheFuture, + datoenInngårISøknadsperioden, + datoErIkkeIEtHull, + detErIngenInnleggelsePåDato, + detErTilsynsbehovPåDatoen, + required, +} from '..'; + +test('required', () => { + const feilmelding = 'Du må oppgi en verdi'; + + expect(required('dette skal fungere')).toBe(true); + expect(required(null)).toBe(feilmelding); + expect(required(undefined)).toBe(feilmelding); + expect(required('')).toBe(feilmelding); +}); + +test('dateIsNotInTheFuture-validator', () => { + expect(dateIsNotInTheFuture(dateConstants.today.toISOString())).toBe(true); + expect(dateIsNotInTheFuture(dateConstants.tomorrow.toISOString())).toBe( + 'Datoen kan ikke settes senere enn dagens dato', + ); +}); + +test('detErTilsynsbehovPåDatoen', () => { + const feilmelding = 'Dato må være innenfor en periode med tilsynsbehov'; + + const datoMedTilsynsbehov = '2020-09-10'; + const perioderMedTilsynsbehov = [new Period('2020-09-01', '2020-09-30')]; + expect(detErTilsynsbehovPåDatoen(datoMedTilsynsbehov, perioderMedTilsynsbehov)).toBe(true); + + const datoUtenTilsynsbehov = '2020-10-01'; + expect(detErTilsynsbehovPåDatoen(datoUtenTilsynsbehov, perioderMedTilsynsbehov)).toBe(feilmelding); +}); + +test('datoenInngårISøknadsperioden', () => { + const feilmelding = 'Dato må være innenfor søknadsperioden'; + const datoISøknadsperioden = '2020-09-10'; + const søknadsperiode = new Period('2020-09-01', '2020-09-30'); + expect(datoenInngårISøknadsperioden(datoISøknadsperioden, søknadsperiode)).toBe(true); + + const datoUtenforSøknadsperioden = '2020-10-01'; + expect(datoenInngårISøknadsperioden(datoUtenforSøknadsperioden, søknadsperiode)).toBe(feilmelding); +}); + +test('detErIngenInnleggelsePåDato', () => { + const feilmelding = 'Dato må være utenfor innleggelsesperioden(e)'; + const datoUtenInnleggelse = '2020-09-10'; + const innleggelsesperioder = [new Period('2020-08-18', '2020-09-08')]; + expect(detErIngenInnleggelsePåDato(datoUtenInnleggelse, innleggelsesperioder)).toBe(true); + + const datoMedInnleggelse = '2020-09-05'; + expect(detErIngenInnleggelsePåDato(datoMedInnleggelse, innleggelsesperioder)).toBe(feilmelding); +}); + +test('datoErIkkeIEtHull', () => { + const resterendeVurderingsperioder = [ + new Period('10.02.2020', '20.02.2020'), + new Period('25.02.2020', '28.02.2020'), + new Period('03.03.2020', '10.03.2020'), + ]; + + const datoIHull = '23.02.2020'; + const datoSkalVæreIHull = datoErIkkeIEtHull(datoIHull, resterendeVurderingsperioder); + expect(datoSkalVæreIHull).toBe('Dato må være innenfor periodene som skal vurderes'); + + const datoUtenforHull = '27.02.2020'; + const datoSkalVæreUtenforHull = datoErIkkeIEtHull(datoUtenforHull, resterendeVurderingsperioder); + expect(datoSkalVæreUtenforHull).toBe(true); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/validators/index.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/validators/index.ts" new file mode 100644 index 0000000000..3673f4f07a --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/ui/form/validators/index.ts" @@ -0,0 +1,94 @@ +import { Period, dateConstants, initializeDate } from '@fpsak-frontend/utils'; +import { Dayjs } from 'dayjs'; +import { finnHullIPerioder } from '../../../util/periodUtils'; + +type InputValue = string | number; + +export function required(v: InputValue): string | boolean { + if (v === null || v === undefined || v === '') { + return 'Du må oppgi en verdi'; + } + return true; +} + +export function dateIsNotInTheFuture(dateString: string): string | boolean { + const date: Dayjs = initializeDate(dateString); + if (date.isSame(dateConstants.tomorrow) || date.isAfter(dateConstants.tomorrow)) { + return 'Datoen kan ikke settes senere enn dagens dato'; + } + return true; +} + +export const detErTilsynsbehovPåDatoen = (dato: string, perioderMedTilsynsbehov: Period[]): string | boolean => { + const detErTilsynsbehovPåDato = perioderMedTilsynsbehov.some(periode => + new Period(periode.fom, periode.tom).includesDate(dato), + ); + if (detErTilsynsbehovPåDato) { + return true; + } + return 'Dato må være innenfor en periode med tilsynsbehov'; +}; + +export const datoenInngårISøknadsperioden = (dato: string, søknadsperiode: Period): string | boolean => { + if (søknadsperiode.includesDate(dato)) { + return true; + } + + return 'Dato må være innenfor søknadsperioden'; +}; + +export const detErIngenInnleggelsePåDato = (dato: string, innleggelsesperioder: Period[]): string | boolean => { + const detErInnleggelsePåDato = innleggelsesperioder.some(periode => + new Period(periode.fom, periode.tom).includesDate(dato), + ); + if (detErInnleggelsePåDato) { + return 'Dato må være utenfor innleggelsesperioden(e)'; + } + return true; +}; + +export const datoErInnenforResterendeVurderingsperioder = ( + dato: string, + resterendeVurderingsperioder: Period[], +): string | true => { + const datoErInnenfor = resterendeVurderingsperioder.some(period => + new Period(period.fom, period.tom).includesDate(dato), + ); + + if (datoErInnenfor) { + return true; + } + + return 'Dato må være innenfor periodene som vurderes'; +}; + +export const datoErIkkeIEtHull = (dato: string, perioder: Period[]): string | true => { + if (perioder.length === 1) { + return true; + } + const hull: Period[] = finnHullIPerioder(perioder); + const datoErIetHull = hull.some(period => period.includesDate(dato)); + + if (datoErIetHull) { + return 'Dato må være innenfor periodene som skal vurderes'; + } + return true; +}; + +export const harBruktDokumentasjon = (dokumenter: []): string | true => { + if (dokumenter.length === 0) { + return 'Du må ha brukt ett eller flere dokumenter i vurderingen'; + } + return true; +}; + +export const fomDatoErFørTomDato = (periode: Period): string | true => { + const fom = initializeDate(periode.fom); + const tom = initializeDate(periode.tom); + + if (fom.isAfter(tom)) { + return 'Fra-dato må være før til-dato'; + } + + return true; +}; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/__tests__/dokumentUtils.spec.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/__tests__/dokumentUtils.spec.ts" new file mode 100644 index 0000000000..37765ea354 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/__tests__/dokumentUtils.spec.ts" @@ -0,0 +1,36 @@ +import Dokument from '../../types/Dokument'; +import { finnBenyttedeDokumenter } from '../dokumentUtils'; + +describe('dokumentUtils', () => { + let result: Dokument[] = []; + + const dokumenter: Partial[] = [ + { + id: '1', + }, + { + id: '2', + }, + { + id: '3', + }, + ]; + + beforeAll(() => { + const valgteDokumentIder: string[] = ['1', '2', '4']; + result = finnBenyttedeDokumenter(valgteDokumentIder, dokumenter as Dokument[]); + }); + + it('should return all documents that are benyttet', () => { + expect(result).toContain(dokumenter[0]); + expect(result).toContain(dokumenter[1]); + }); + + it('should not return any documents that are not benyttet', () => { + expect(result).not.toContain(dokumenter[2]); + }); + + it('should not return any documents that are benyttet but is not contained in the list of documents', () => { + expect(result.some(dokument => dokument.id === '4')).toBe(false); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/__tests__/periodeUtils.spec.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/__tests__/periodeUtils.spec.ts" new file mode 100644 index 0000000000..9bf8d0e120 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/__tests__/periodeUtils.spec.ts" @@ -0,0 +1,128 @@ +import * as periodUtils from '@fpsak-frontend/utils'; +import { finnMaksavgrensningerForPerioder, finnHullIPerioder, slåSammenSammenhengendePerioder } from '../periodUtils'; + +const { Period } = periodUtils; + +describe('slåSammenSammenhengendePerioder', () => { + it('should handle two consecutive periods with one separate', () => { + const periods = [ + new Period('01.01.2020', '05.01.2020'), + new Period('06.01.2020', '10.01.2020'), + new Period('25.01.2020', '31.01.2020'), + new Period('01.02.2020', '05.02.2020'), + new Period('07.02.2020', '10.02.2020'), + ]; + + const expectedResult = [ + new Period('01.01.2020', '10.01.2020'), + new Period('25.01.2020', '05.02.2020'), + new Period('07.02.2020', '10.02.2020'), + ]; + + const result = slåSammenSammenhengendePerioder(periods); + + expect(result).toEqual(expectedResult); + }); + + it('should handle two consecutive periods with one separate in the middle', () => { + const periods = [ + new Period('01.01.2020', '05.01.2020'), + new Period('06.01.2020', '10.01.2020'), + new Period('25.01.2020', '30.01.2020'), + new Period('01.02.2020', '05.02.2020'), + new Period('06.02.2020', '10.02.2020'), + ]; + + const expectedResult = [ + new Period('01.01.2020', '10.01.2020'), + new Period('25.01.2020', '30.01.2020'), + new Period('01.02.2020', '10.02.2020'), + ]; + + const result = slåSammenSammenhengendePerioder(periods); + + expect(result).toEqual(expectedResult); + }); + + it('should handle the first period being separate', () => { + const periods = [ + new Period('01.01.2020', '05.01.2020'), + new Period('01.02.2020', '05.02.2020'), + new Period('06.02.2020', '10.02.2020'), + ]; + + const expectedResult = [new Period('01.01.2020', '05.01.2020'), new Period('01.02.2020', '10.02.2020')]; + + const result = slåSammenSammenhengendePerioder(periods); + + expect(result).toEqual(expectedResult); + }); + + it('should handle two separate periods', () => { + const periods = [new Period('01.01.2020', '05.01.2020'), new Period('06.02.2020', '10.02.2020')]; + + const expectedResult = [new Period('01.01.2020', '05.01.2020'), new Period('06.02.2020', '10.02.2020')]; + + const result = slåSammenSammenhengendePerioder(periods); + + expect(result).toEqual(expectedResult); + }); + + it('should handle overlapping periods', () => { + const periods = [ + new Period('01.01.2020', '07.01.2020'), + new Period('06.01.2020', '10.01.2020'), + new Period('16.01.2020', '20.01.2020'), + ]; + + const expectedResult = [new Period('01.01.2020', '10.01.2020'), new Period('16.01.2020', '20.01.2020')]; + + const result = slåSammenSammenhengendePerioder(periods); + + expect(result).toEqual(expectedResult); + }); + + it('should handle three consecutive periods', () => { + const periods = [ + new Period('01.01.2020', '05.01.2020'), + new Period('06.01.2020', '10.01.2020'), + new Period('11.01.2020', '20.01.2020'), + ]; + + const expectedResult = [new Period('01.01.2020', '20.01.2020')]; + + const result = slåSammenSammenhengendePerioder(periods); + + expect(result).toEqual(expectedResult); + }); + + it('should handle three overlapping periods', () => { + const periods = [ + new Period('01.01.2020', '07.01.2020'), + new Period('06.01.2020', '15.01.2020'), + new Period('11.01.2020', '20.01.2020'), + ]; + + const expectedResult = [new Period('01.01.2020', '20.01.2020')]; + + const result = slåSammenSammenhengendePerioder(periods); + + expect(result).toEqual(expectedResult); + }); +}); + +test('finnHullIPeriode', () => { + const perioder = [new Period('2020-04-01', '06.04.2020'), new Period('2020-02-01', '06.02.2020')]; + const expectedHull = [new Period('2020-02-07', '2020-03-31')]; + const result = finnHullIPerioder(perioder); + + expect(result).toEqual(expectedHull); +}); + +test('finnAvgrensningerForSøknapdsperioder', () => { + const perioder = [new Period('2020-04-01', '2020-04-30'), new Period('2020-04-10', '2020-04-28')]; + const expectedResult = new Period('2020-04-01', '2020-04-30'); + const result = finnMaksavgrensningerForPerioder(perioder); + + expect(result).toEqual(expectedResult); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/__tests__/statusUtils.spec.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/__tests__/statusUtils.spec.ts" new file mode 100644 index 0000000000..5517ea0334 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/__tests__/statusUtils.spec.ts" @@ -0,0 +1,40 @@ +import { finnNesteStegForPleiepenger } from '../statusUtils'; +import StatusResponse from '../../types/SykdomsstegStatusResponse'; +import { StepId } from '../../types/Step'; + +describe('statusUtils', () => { + describe('finnNesteSteg', () => { + describe('dokumentsteget', () => { + it('harUklassifiserteDokumenter=true should give Step.Dokument', () => { + const nesteSteg = finnNesteStegForPleiepenger({ harUklassifiserteDokumenter: true } as StatusResponse); + expect(nesteSteg.id).toBe(StepId.Dokument); + }); + + it('manglerDiagnosekode=true should give Step.Dokument', () => { + const nesteSteg = finnNesteStegForPleiepenger({ manglerDiagnosekode: true } as StatusResponse); + expect(nesteSteg.id).toBe(StepId.Dokument); + }); + + it('manglerGodkjentLegeerklæring=true should give Step.Dokument', () => { + const nesteSteg = finnNesteStegForPleiepenger({ manglerGodkjentLegeerklæring: true } as StatusResponse); + expect(nesteSteg.id).toBe(StepId.Dokument); + }); + }); + + describe('tilsynOgPleie-steget', () => { + it('manglerVurderingAvKontinuerligTilsynOgPleie should give Step.TilsynOgPleie', () => { + const nesteSteg = finnNesteStegForPleiepenger({ + manglerVurderingAvKontinuerligTilsynOgPleie: true, + } as StatusResponse); + expect(nesteSteg.id).toBe(StepId.TilsynOgPleie); + }); + }); + + describe('toOmsorgspersoner-steget', () => { + it('manglerVurderingAvToOmsorgspersoner should give Step.TilsynOgPleie', () => { + const nesteSteg = finnNesteStegForPleiepenger({ manglerVurderingAvToOmsorgspersoner: true } as StatusResponse); + expect(nesteSteg.id).toBe(StepId.ToOmsorgspersoner); + }); + }); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/diagnosekodeSearcher.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/diagnosekodeSearcher.ts" new file mode 100644 index 0000000000..d0c563591f --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/diagnosekodeSearcher.ts" @@ -0,0 +1,23 @@ +// This module is here to load and initialize a DiagnosekodeSearcher instance the replaces the external diagnosekode-api service. +// Instead of querying the external service we load all diagnose codes into a searcher class instance from the @navikt/diagnosekoder +// package, and do the lookup of diagnose codes agains this instance directly. +// +// This import is a 1.3MB package, so we import it asynchronously as a separate bundle. +// Should then not impact loading time too much since it will be cached and only reloaded on first load, or once a year. + +import type { DiagnosekodeSearcher, ICD10Diagnosekode } from '@navikt/diagnosekoder'; +import Diagnosekode from '../types/Diagnosekode'; + +export type DiagnosekodeSearcherPromise = Promise>; + +const initDiagnosekodeSearcher = async (pageSize: number): DiagnosekodeSearcherPromise => + import('@navikt/diagnosekoder').then( + diagnosekodeModule => new diagnosekodeModule.DiagnosekodeSearcher(diagnosekodeModule.ICD10, pageSize), + ); + +export const toLegacyDiagnosekode = (icd10Code: ICD10Diagnosekode): Diagnosekode => ({ + kode: icd10Code.code, + beskrivelse: icd10Code.text, +}); + +export default initDiagnosekodeSearcher; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/dokumentUtils.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/dokumentUtils.ts" new file mode 100644 index 0000000000..84c1e5fd4a --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/dokumentUtils.ts" @@ -0,0 +1,38 @@ +import { initializeDate } from '@fpsak-frontend/utils'; +import { Dokument } from '../types/Dokument'; +import { + StrukturerDokumentFormFieldName as FieldName, + StrukturerDokumentFormState, +} from '../types/StrukturerDokumentFormState'; + +export const finnBenyttedeDokumenter = (benyttedeDokumentIder: string[], alleDokumenter: Dokument[]): Dokument[] => + alleDokumenter.filter(dokument => benyttedeDokumentIder.includes(dokument.id)); + +export const lagStrukturertDokument = (formState: StrukturerDokumentFormState, dokument: Dokument): Dokument => ({ + ...dokument, + type: formState[FieldName.INNEHOLDER_MEDISINSKE_OPPLYSNINGER], + datert: formState[FieldName.DATERT], + duplikatAvId: formState[FieldName.DUPLIKAT_AV_ID] === 'ikkeDuplikat' ? null : formState[FieldName.DUPLIKAT_AV_ID], +}); + +export const dokumentSorter = (dok1: Dokument, dok2: Dokument): number => { + const dok1Date = initializeDate(dok1.datert || dok1.mottattTidspunkt); + const dok2Date = initializeDate(dok2.datert || dok2.mottattTidspunkt); + if (dok1Date.isBefore(dok2Date)) { + return 1; + } + if (dok2Date.isBefore(dok1Date)) { + return -1; + } + + if (dok1Date.isSame(dok2Date)) { + if (dok1.id > dok2.id) { + return 1; + } + + if (dok2.id > dok1.id) { + return -1; + } + } + return 0; +}; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/hooks.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/hooks.ts" new file mode 100644 index 0000000000..2c6ea06681 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/hooks.ts" @@ -0,0 +1,14 @@ +import { useEffect, useRef } from 'react'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyType = any; + +const usePrevious = (value: AnyType): undefined => { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }); + return ref.current; +}; + +export default usePrevious; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/linkUtils.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/linkUtils.ts" new file mode 100644 index 0000000000..bd9c4ef2c6 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/linkUtils.ts" @@ -0,0 +1,6 @@ +import LinkRel from '../constants/LinkRel'; +import Link from '../types/Link'; + +export const findLinkByRel = (linkRel: LinkRel, links: Link[]): Link => links.find(({ rel }) => rel === linkRel); + +export const findHrefByRel = (linkRel: LinkRel, links: Link[]): string => findLinkByRel(linkRel, links)?.href; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/periodUtils.spec.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/periodUtils.spec.ts" new file mode 100644 index 0000000000..0a0c0b1520 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/periodUtils.spec.ts" @@ -0,0 +1,81 @@ +import { Period } from '@fpsak-frontend/utils'; +import { slåSammenSammenhengendePerioder } from './periodUtils'; + +const sammenhengendePerioder = [ + { fom: '2021-06-14', tom: '2021-06-20' }, + { fom: '2021-06-21', tom: '2021-06-30' }, + { fom: '2021-07-01', tom: '2021-09-01' }, + { fom: '2021-09-02', tom: '2021-09-10' }, + { fom: '2021-09-11', tom: '2021-09-15' }, + { fom: '2021-09-16', tom: '2021-09-20' }, + { fom: '2021-09-21', tom: '2021-10-14' }, + { fom: '2021-10-15', tom: '2021-10-22' }, + { fom: '2021-10-23', tom: '2021-10-29' }, + { fom: '2021-10-30', tom: '2021-12-07' }, + { fom: '2021-12-08', tom: '2021-12-08' }, + { fom: '2021-12-09', tom: '2021-12-13' }, + { fom: '2021-12-14', tom: '2021-12-21' }, + { fom: '2021-12-22', tom: '2021-12-31' }, + { fom: '2022-01-01', tom: '2022-02-01' }, + { fom: '2022-02-02', tom: '2022-02-09' }, + { fom: '2022-02-10', tom: '2022-03-02' }, + { fom: '2022-03-03', tom: '2022-03-29' }, + { fom: '2022-03-30', tom: '2022-04-27' }, + { fom: '2022-04-28', tom: '2022-06-10' }, +].map(period => new Period(period.fom, period.tom)); + +const overlappendePerioder = [ + { fom: '2021-06-14', tom: '2021-06-25' }, + { fom: '2021-06-21', tom: '2021-07-01' }, + { fom: '2021-07-01', tom: '2021-09-09' }, + { fom: '2021-09-02', tom: '2021-09-10' }, +].map(period => new Period(period.fom, period.tom)); + +const perioderSomIkkeHengerSammen = [ + { fom: '2021-06-14', tom: '2021-06-17' }, + { fom: '2021-06-21', tom: '2021-06-25' }, + { fom: '2021-07-01', tom: '2021-09-01' }, + { fom: '2021-09-06', tom: '2021-09-10' }, +].map(period => new Period(period.fom, period.tom)); + +const blandedePerioder = [ + { fom: '2021-06-14', tom: '2021-06-25' }, + { fom: '2021-06-21', tom: '2021-07-01' }, + { fom: '2021-07-01', tom: '2021-09-09' }, + { fom: '2021-09-12', tom: '2021-09-17' }, + { fom: '2021-09-18', tom: '2021-09-25' }, + { fom: '2021-09-27', tom: '2021-09-30' }, + { fom: '2021-10-02', tom: '2021-10-07' }, +].map(period => new Period(period.fom, period.tom)); + +describe('periodUtils', () => { + test('slår sammen sammenhengende perioder', () => { + const perioder = slåSammenSammenhengendePerioder(sammenhengendePerioder); + expect(perioder.length).toEqual(1); + expect(perioder[0].fom).toEqual('2021-06-14'); + expect(perioder[0].tom).toEqual('2022-06-10'); + }); + test('slår sammen overlappende perioder', () => { + const perioder = slåSammenSammenhengendePerioder(overlappendePerioder); + expect(perioder.length).toEqual(1); + expect(perioder[0].fom).toEqual('2021-06-14'); + expect(perioder[0].tom).toEqual('2021-09-10'); + }); + test('slår ikke sammen perioder som ikke henger sammen', () => { + const perioder = slåSammenSammenhengendePerioder(perioderSomIkkeHengerSammen); + expect(perioder.length).toEqual(4); + expect(perioder.every((periode, index) => periode === perioderSomIkkeHengerSammen[index])).toEqual(true); + }); + test('håndterer at noen perioder slås sammen og andre ikke', () => { + const perioder = slåSammenSammenhengendePerioder(blandedePerioder); + expect(perioder.length).toEqual(4); + expect(perioder[0].fom).toEqual(blandedePerioder[0].fom); + expect(perioder[0].tom).toEqual(blandedePerioder[2].tom); + expect(perioder[1].fom).toEqual(blandedePerioder[3].fom); + expect(perioder[1].tom).toEqual(blandedePerioder[4].tom); + expect(perioder[2].fom).toEqual(blandedePerioder[5].fom); + expect(perioder[2].tom).toEqual(blandedePerioder[5].tom); + expect(perioder[3].fom).toEqual(blandedePerioder[6].fom); + expect(perioder[3].tom).toEqual(blandedePerioder[6].tom); + }); +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/periodUtils.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/periodUtils.ts" new file mode 100644 index 0000000000..fdc405a8e4 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/periodUtils.ts" @@ -0,0 +1,135 @@ +import { Period, initializeDate, isSameOrBefore, sortPeriodsByFomDate } from '@fpsak-frontend/utils'; + +const checkIfPeriodsAreEdgeToEdge = (period, otherPeriod) => { + const dayAfterPeriod = initializeDate(period.tom).add(1, 'day'); + const startOfNextPeriod = initializeDate(otherPeriod.fom); + return dayAfterPeriod.isSame(startOfNextPeriod); +}; + +export const slåSammenSammenhengendePerioder = (periods: Period[]): Period[] => { + if (!periods || periods.length === 0) { + return []; + } + + const sortedPeriods = periods.sort((p1, p2) => sortPeriodsByFomDate(p1, p2)); + const combinedPeriods: Period[] = []; + + const getFirstDate = (date1: string, date2: string) => { + if (isSameOrBefore(date1, date2)) { + return date1; + } + + return date2; + }; + + const getLastDate = (date1: string, date2: string) => { + if (isSameOrBefore(date1, date2)) { + return date2; + } + + return date1; + }; + + const checkIfPeriodCanBeCombinedWithPreviousPeriod = (period: Period, previousPeriod?: Period) => { + if (!previousPeriod) { + return false; + } + const hasOverlapWithPreviousPeriod = previousPeriod.includesDate(period.fom); + const periodsAreEdgeToEdge = checkIfPeriodsAreEdgeToEdge(previousPeriod, period); + return hasOverlapWithPreviousPeriod || periodsAreEdgeToEdge; + }; + + const combinePeriods = (period, otherPeriod) => { + const firstFom = getFirstDate(period.fom, otherPeriod.fom); + const lastTom = getLastDate(period.tom, otherPeriod.tom); + const combinedPeriod = new Period(firstFom, lastTom); + return combinedPeriod; + }; + + const addToListIfNotAdded = (period: Period) => { + const previousPeriod = combinedPeriods[combinedPeriods.length - 1]; + const canBeCombinedWithPreviousPeriod = checkIfPeriodCanBeCombinedWithPreviousPeriod(period, previousPeriod); + + if (canBeCombinedWithPreviousPeriod) { + const combinedPeriod = combinePeriods(period, previousPeriod); + combinedPeriods[combinedPeriods.length - 1] = combinedPeriod; + } else { + combinedPeriods.push(period); + } + }; + + const periodsToSkip = []; + + const maybeCombinePeriods = (period, nextPeriod, index, array, previouslySkippedCounter = 0) => { + if (nextPeriod) { + const hasOverlapWithNextPeriod = nextPeriod.includesDate(period.tom); + const periodsAreEdgeToEdge = checkIfPeriodsAreEdgeToEdge(period, nextPeriod); + if (hasOverlapWithNextPeriod || periodsAreEdgeToEdge) { + const combinedPeriod = combinePeriods(period, nextPeriod); + periodsToSkip.push(nextPeriod); + const periodsToSkipCounter = previouslySkippedCounter + 1; + const next = array[index + periodsToSkipCounter]; + if (next) { + maybeCombinePeriods(combinedPeriod, next, index, array, periodsToSkipCounter); + } else { + combinedPeriods.push(combinedPeriod); + } + } else { + addToListIfNotAdded(period); + } + } else { + addToListIfNotAdded(period); + } + }; + + sortedPeriods.forEach((period, index, array) => { + const nextPeriod = array[index + 1]; + if (!periodsToSkip.includes(nextPeriod)) { + maybeCombinePeriods(period, nextPeriod, index, array); + } + }); + return combinedPeriods; +}; + +export const finnHullIPerioder = (periode: Period[]): Period[] => { + const hull: Period[] = []; + const sortedPeriods = periode.sort((p1, p2) => sortPeriodsByFomDate(p1, p2)); + + sortedPeriods.forEach((period, index, array) => { + const nextPeriod = array[index + 1]; + if (nextPeriod) { + if (!checkIfPeriodsAreEdgeToEdge(period, nextPeriod) && !nextPeriod.includesDate(period.tom)) { + const dayAfterPeriod = initializeDate(period.tom).add(1, 'day').format('YYYY-MM-DD'); + const dayBeforeStartOfNextPeriod = initializeDate(nextPeriod.fom).subtract(1, 'day').format('YYYY-MM-DD'); + const nyttHull = new Period(dayAfterPeriod, dayBeforeStartOfNextPeriod); + hull.push(nyttHull); + } + } + }); + return hull; +}; + +export const finnMaksavgrensningerForPerioder = (perioder: Period[]): Period => { + let maksimalSøknadsperiode: Period = null; + perioder.forEach(periode => { + let nyFom; + let nyTom; + if (!maksimalSøknadsperiode) { + maksimalSøknadsperiode = new Period(periode.fom, periode.tom); + } else { + if (periode.startsBefore(maksimalSøknadsperiode)) { + nyFom = periode.fom; + } + + if (periode.endsAfter(maksimalSøknadsperiode)) { + nyTom = periode.tom; + } + + if (nyFom || nyTom) { + maksimalSøknadsperiode = new Period(nyFom || maksimalSøknadsperiode.fom, nyTom || maksimalSøknadsperiode.tom); + } + } + }); + + return maksimalSøknadsperiode; +}; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/renderers.tsx" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/renderers.tsx" new file mode 100644 index 0000000000..54a59b81f9 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/renderers.tsx" @@ -0,0 +1,23 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import MainComponent from '../ui/MainComponent'; +import ContainerContract from '../types/ContainerContract'; + +function prepare() { + if (process.env.NODE_ENV !== 'production') { + return import('../mocks/browser').then(({ worker }) => worker.start({ onUnhandledRequest: 'bypass' })); + } + + return Promise.resolve(); +} + +const renderAppInSuccessfulState = (appId: string, data: ContainerContract): Promise => + prepare().then(() => { + const container = document.getElementById(appId); + const root = createRoot(container); + root.render(); + }); + +export default { + renderAppInSuccessfulState, +}; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/statusUtils.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/statusUtils.ts" new file mode 100644 index 0000000000..14d41bb60a --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/statusUtils.ts" @@ -0,0 +1,118 @@ +import StatusResponse from '../types/SykdomsstegStatusResponse'; +import Step, { + dokumentSteg, + langvarigSykdomSteg, + livetsSluttfaseSteg, + opplæringspengerDokumentSteg, + sluttfaseDokumentSteg, + tilsynOgPleieSteg, + toOmsorgspersonerSteg, +} from '../types/Step'; +import FagsakYtelseType from '../constants/FagsakYtelseType'; + +type Steg = typeof dokumentSteg | typeof tilsynOgPleieSteg | typeof toOmsorgspersonerSteg; + +export const stegForSakstype = (fagsakYtelseType: FagsakYtelseType): Step[] => { + if (fagsakYtelseType === FagsakYtelseType.OPPLÆRINGSPENGER) { + return [opplæringspengerDokumentSteg, langvarigSykdomSteg]; + } + if (fagsakYtelseType === FagsakYtelseType.PLEIEPENGER_SLUTTFASE) { + return [sluttfaseDokumentSteg, livetsSluttfaseSteg]; + } + return [dokumentSteg, tilsynOgPleieSteg, toOmsorgspersonerSteg]; +}; + +export const finnNesteStegForPleiepenger = ( + { + kanLøseAksjonspunkt, + harUklassifiserteDokumenter, + manglerDiagnosekode, + manglerVurderingAvKontinuerligTilsynOgPleie, + manglerVurderingAvToOmsorgspersoner, + manglerGodkjentLegeerklæring, + nyttDokumentHarIkkekontrollertEksisterendeVurderinger, + }: StatusResponse, + isOnMount?: boolean, +): Steg => { + if (harUklassifiserteDokumenter || manglerDiagnosekode || manglerGodkjentLegeerklæring) { + return dokumentSteg; + } + + if (manglerVurderingAvKontinuerligTilsynOgPleie || nyttDokumentHarIkkekontrollertEksisterendeVurderinger) { + return tilsynOgPleieSteg; + } + + if (manglerVurderingAvToOmsorgspersoner) { + return toOmsorgspersonerSteg; + } + + if (kanLøseAksjonspunkt && !isOnMount) { + return tilsynOgPleieSteg; + } + + return null; +}; + +export const finnNesteStegForLivetsSluttfase = ( + { + kanLøseAksjonspunkt, + harUklassifiserteDokumenter, + manglerGodkjentLegeerklæring, + manglerVurderingAvILivetsSluttfase, + nyttDokumentHarIkkekontrollertEksisterendeVurderinger, + }: StatusResponse, + isOnMount?: boolean, +): Steg => { + if (harUklassifiserteDokumenter || manglerGodkjentLegeerklæring) { + return sluttfaseDokumentSteg; + } + + if (manglerVurderingAvILivetsSluttfase || nyttDokumentHarIkkekontrollertEksisterendeVurderinger) { + return livetsSluttfaseSteg; + } + + if (kanLøseAksjonspunkt && !isOnMount) { + return livetsSluttfaseSteg; + } + + return null; +}; +export const finnNesteStegForOpplæringspenger = ( + { + kanLøseAksjonspunkt, + harUklassifiserteDokumenter, + manglerGodkjentLegeerklæring, + manglerVurderingAvLangvarigSykdom, + nyttDokumentHarIkkekontrollertEksisterendeVurderinger, + }: StatusResponse, + isOnMount?: boolean, +): Steg => { + if (harUklassifiserteDokumenter || manglerGodkjentLegeerklæring) { + return opplæringspengerDokumentSteg; + } + + if (manglerVurderingAvLangvarigSykdom || nyttDokumentHarIkkekontrollertEksisterendeVurderinger) { + return langvarigSykdomSteg; + } + + if (kanLøseAksjonspunkt && !isOnMount) { + return langvarigSykdomSteg; + } + + return opplæringspengerDokumentSteg; +}; + +export const nesteStegErVurderingForPleiepenger = (sykdomsstegStatus: StatusResponse): boolean => { + const nesteSteg = finnNesteStegForPleiepenger(sykdomsstegStatus); + return nesteSteg === tilsynOgPleieSteg || nesteSteg === toOmsorgspersonerSteg; +}; + +export const nesteStegErLivetssluttfase = (sykdomsstegStatus: StatusResponse): boolean => { + const nesteSteg = finnNesteStegForLivetsSluttfase(sykdomsstegStatus); + return nesteSteg === livetsSluttfaseSteg; +}; + +export const nesteStegErOpplæringspenger = (sykdomsstegStatus: StatusResponse): boolean => { + const nesteSteg = finnNesteStegForOpplæringspenger(sykdomsstegStatus); + return nesteSteg === langvarigSykdomSteg; +}; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/utils.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/utils.ts" new file mode 100644 index 0000000000..f2ffc2c5cf --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/utils.ts" @@ -0,0 +1,5 @@ +import FagsakYtelseType from '../constants/FagsakYtelseType'; + +// eslint-disable-next-line import/prefer-default-export +export const erFagsakOLPEllerPLS = (fagsakYtelseType: FagsakYtelseType) => + [FagsakYtelseType.OPPLÆRINGSPENGER, FagsakYtelseType.PLEIEPENGER_SLUTTFASE].includes(fagsakYtelseType); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/src/util/viewUtils.ts" "b/packages/fakta-medisinsk-vilk\303\245r/src/util/viewUtils.ts" new file mode 100644 index 0000000000..41b3895384 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/src/util/viewUtils.ts" @@ -0,0 +1,8 @@ +const scrollUp = (): void => { + const element = document.getElementById('medisinskVilkår'); + const elementOffset = element?.offsetTop || 0; + + window.scroll(0, elementOffset - 50); +}; + +export default scrollUp; diff --git "a/packages/fakta-medisinsk-vilk\303\245r/webpack/webpack-config.development.js" "b/packages/fakta-medisinsk-vilk\303\245r/webpack/webpack-config.development.js" new file mode 100644 index 0000000000..151f65b99d --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/webpack/webpack-config.development.js" @@ -0,0 +1,40 @@ +/* eslint-disable import/extensions */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const webpack = require('webpack'); +const path = require('path'); +const { merge } = require('webpack-merge'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const WebpackDevServer = require('webpack-dev-server'); +const commonWebpackConfig = require('./webpack.common.js'); + +const webpackConfig = merge(commonWebpackConfig, { + entry: `${path.resolve(__dirname, '../', 'src')}/dev/app.ts`, + mode: 'development', + devtool: 'eval-cheap-module-source-map', + plugins: [ + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, '../index.html'), + }), + ], +}); + +const port = 8484; +const devServerOptions = { + hot: true, + headers: { + 'Access-Control-Allow-Origin': 'http://localhost:9000', + }, + port, +}; + +const compiler = webpack(webpackConfig); +const devServer = new WebpackDevServer(devServerOptions, compiler); +compiler.close(() => console.info('Compiler closed')); + +devServer.startCallback(error => { + if (error) { + console.error(error); + } else { + console.log(`Listening at port ${port}`); + } +}); diff --git "a/packages/fakta-medisinsk-vilk\303\245r/webpack/webpack.common.js" "b/packages/fakta-medisinsk-vilk\303\245r/webpack/webpack.common.js" new file mode 100644 index 0000000000..ffc2e21ed2 --- /dev/null +++ "b/packages/fakta-medisinsk-vilk\303\245r/webpack/webpack.common.js" @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const process = require('process'); +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const cssExtractLoaderConfig = { + loader: MiniCssExtractPlugin.loader, +}; + +const nodeModules = path.resolve(__dirname, '../node_modules'); +const rootNodeModules = path.resolve(__dirname, '../../../node_modules'); + +module.exports = { + resolve: { + extensions: ['.ts', '.tsx', '.js', '.css'], + }, + module: { + rules: [ + { + test: /\.(ts|js)x?$/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + rootMode: 'upward', + }, + }, + }, + { + test: /\.css$/, + use: [ + cssExtractLoaderConfig, + { + loader: 'css-loader', + options: { + importLoaders: 1, + modules: { + localIdentName: '[name]_[local]_[contenthash:base64:5]', + }, + }, + }, + ], + exclude: [nodeModules, rootNodeModules], + }, + { + test: /\.(less|css)?$/, + use: [ + cssExtractLoaderConfig, + { + loader: 'css-loader', + }, + { + loader: 'less-loader', + options: { + lessOptions: { + modifyVars: { + nodeModulesPath: '~', + coreModulePath: '~', + }, + }, + }, + }, + ], + include: [nodeModules, rootNodeModules], + }, + { + test: /\.(jpg|png|svg)$/, + loader: 'file-loader', + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: 'styles.css', + }), + ], +}; diff --git a/packages/fakta-medlemskap/package.json b/packages/fakta-medlemskap/package.json index e0e735571e..ab711612b1 100644 --- a/packages/fakta-medlemskap/package.json +++ b/packages/fakta-medlemskap/package.json @@ -12,7 +12,7 @@ "@fpsak-frontend/shared-components": "1.0.0", "@fpsak-frontend/utils": "1.0.0", "@k9-sak-web/fakta-felles": "1.0.0", - "@navikt/ft-plattform-komponenter": "2.3.10", + "@navikt/ft-plattform-komponenter": "2.3.14", "i18n-iso-countries": "7.7.0", "moment": "2.29.4", "nav-frontend-core": "6.0.1", @@ -26,7 +26,7 @@ "nav-frontend-typografi-style": "2.0.2", "prop-types": "15.8.1", "react": "18.2.0", - "react-intl": "6.5.2", + "react-intl": "6.5.5", "react-redux": "8.1.3", "redux": "4.2.1", "redux-form": "8.3.10", diff --git a/packages/fakta-om-barnet/index.html b/packages/fakta-om-barnet/index.html new file mode 100644 index 0000000000..d1130ae0d8 --- /dev/null +++ b/packages/fakta-om-barnet/index.html @@ -0,0 +1,25 @@ + + + Om barnet + + + +
    + + + diff --git a/packages/fakta-om-barnet/index.ts b/packages/fakta-om-barnet/index.ts new file mode 100644 index 0000000000..135ceb40ed --- /dev/null +++ b/packages/fakta-om-barnet/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as OmBarnet } from './src/ui/MainComponent'; diff --git a/packages/fakta-om-barnet/mock/api-mock.ts b/packages/fakta-om-barnet/mock/api-mock.ts new file mode 100644 index 0000000000..5e7e0b1c94 --- /dev/null +++ b/packages/fakta-om-barnet/mock/api-mock.ts @@ -0,0 +1,26 @@ +/* eslint-disable import/prefer-default-export */ +import { rest } from 'msw'; + +export const handlers = [ + rest.get('http://localhost:8082/mock/rettVedDod', (req, res, ctx) => + res( + ctx.status(200), + ctx.json({ + vurdering: 'dette er en vurdering', + rettVedDødType: 'RETT_12_UKER', + }), + ), + ), + + rest.get('http://localhost:8082/mock/omPleietrengende', (req, res, ctx) => + res( + ctx.status(200), + ctx.json({ + fnr: '012345678912', + navn: 'DUCK DOLE', + diagnosekoder: ['R619', 'A300'], + dodsdato: '2021-05-26', + }), + ), + ), +]; diff --git a/packages/fakta-om-barnet/mock/browser.ts b/packages/fakta-om-barnet/mock/browser.ts new file mode 100644 index 0000000000..4ac10c54c5 --- /dev/null +++ b/packages/fakta-om-barnet/mock/browser.ts @@ -0,0 +1,6 @@ +/* eslint-disable import/prefer-default-export */ +/* eslint-disable import/no-extraneous-dependencies */ +import { setupWorker } from 'msw'; +import { handlers } from './api-mock'; +// This configures a Service Worker with the given request handlers. +export const worker = setupWorker(...handlers); diff --git a/packages/fakta-om-barnet/package.json b/packages/fakta-om-barnet/package.json new file mode 100644 index 0000000000..01583fa246 --- /dev/null +++ b/packages/fakta-om-barnet/package.json @@ -0,0 +1,32 @@ +{ + "name": "@k9-sak-web/fakta-om-barnet", + "version": "1.0.0", + "module": "index.ts", + "keywords": [], + "author": "NAV IT", + "license": "MIT", + "private": true, + "scripts": { + "dev": "node webpack/webpack-config.development.js" + }, + "dependencies": { + "@navikt/ds-css": "5.11.2", + "@navikt/ds-react": "5.11.2", + "@navikt/fnrvalidator": "1.3.3", + "@navikt/ft-plattform-komponenter": "2.3.14", + "@popperjs/core": "2.11.8", + "axios": "1.6.2", + "classnames": "2.3.2", + "dayjs": "1.11.10", + "lodash.throttle": "4.1.1", + "react": "18.2.0", + "react-collapse": "5.1.1", + "react-dom": "18.2.0", + "react-hook-form": "7.48.2", + "react-popper": "2.3.0", + "tailwindcss": "^3.3.5" + }, + "msw": { + "workerDirectory": "public" + } +} diff --git a/packages/fakta-om-barnet/postcss.config.js b/packages/fakta-om-barnet/postcss.config.js new file mode 100644 index 0000000000..fa41216bff --- /dev/null +++ b/packages/fakta-om-barnet/postcss.config.js @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const tailwindcss = require('tailwindcss'); +const autoprefixer = require('autoprefixer'); +const postcssImport = require('postcss-import'); + +module.exports = { + plugins: [postcssImport, tailwindcss('./tailwind.config.js'), autoprefixer], +}; diff --git a/packages/fakta-om-barnet/public/mockServiceWorker.js b/packages/fakta-om-barnet/public/mockServiceWorker.js new file mode 100644 index 0000000000..95835ef353 --- /dev/null +++ b/packages/fakta-om-barnet/public/mockServiceWorker.js @@ -0,0 +1,302 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (1.3.2). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'; +const activeClientIds = new Set(); + +self.addEventListener('install', function () { + self.skipWaiting(); +}); + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener('message', async function (event) { + const clientId = event.source.id; + + if (!clientId || !self.clients) { + return; + } + + const client = await self.clients.get(clientId); + + if (!client) { + return; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }); + break; + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }); + break; + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId); + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }); + break; + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId); + break; + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId); + + const remainingClients = allClients.filter(client => { + return client.id !== clientId; + }); + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister(); + } + + break; + } + } +}); + +self.addEventListener('fetch', function (event) { + const { request } = event; + const accept = request.headers.get('accept') || ''; + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return; + } + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return; + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return; + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return; + } + + // Generate unique request ID. + const requestId = Math.random().toString(16).slice(2); + + event.respondWith( + handleRequest(event, requestId).catch(error => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ); + return; + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ); + }), + ); +}); + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event); + const response = await getResponse(event, client, requestId); + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + (async function () { + const clonedResponse = response.clone(); + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: clonedResponse.body === null ? null : await clonedResponse.text(), + headers: Object.fromEntries(clonedResponse.headers.entries()), + redirected: clonedResponse.redirected, + }, + }); + })(); + } + + return response; +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId); + + if (client?.frameType === 'top-level') { + return client; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + return allClients + .filter(client => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible'; + }) + .find(client => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id); + }); +} + +async function getResponse(event, client, requestId) { + const { request } = event; + const clonedRequest = request.clone(); + + function passthrough() { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const headers = Object.fromEntries(clonedRequest.headers.entries()); + + // Remove MSW-specific request headers so the bypassed requests + // comply with the server's CORS preflight check. + // Operate with the headers as an object because request "Headers" + // are immutable. + delete headers['x-msw-bypass']; + + return fetch(clonedRequest, { headers }); + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough(); + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough(); + } + + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough(); + } + + // Notify the client that a request has been intercepted. + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.text(), + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }); + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data); + } + + case 'MOCK_NOT_FOUND': { + return passthrough(); + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.data; + const networkError = new Error(message); + networkError.name = name; + + // Rejecting a "respondWith" promise emulates a network error. + throw networkError; + } + } + + return passthrough(); +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel(); + + channel.port1.onmessage = event => { + if (event.data && event.data.error) { + return reject(event.data.error); + } + + resolve(event.data); + }; + + client.postMessage(message, [channel.port2]); + }); +} + +function sleep(timeMs) { + return new Promise(resolve => { + setTimeout(resolve, timeMs); + }); +} + +async function respondWithMock(response) { + await sleep(response.delay); + return new Response(response.body, response); +} diff --git a/packages/fakta-om-barnet/src/dev/app.ts b/packages/fakta-om-barnet/src/dev/app.ts new file mode 100644 index 0000000000..408d923ce3 --- /dev/null +++ b/packages/fakta-om-barnet/src/dev/app.ts @@ -0,0 +1,13 @@ +import renderers from '../util/renderers'; +import './devStyles.css'; +import ContainerContract from '../types/ContainerContract'; +import '@navikt/ft-plattform-komponenter/dist/style.css'; + +interface ExtendedWindow extends Window { + renderOmBarnetApp: (id: string, contract: ContainerContract) => void; +} + +(window as Partial).renderOmBarnetApp = async (appId, data) => { + const { renderAppInSuccessfulState } = renderers; + renderAppInSuccessfulState(appId, data); +}; diff --git a/packages/fakta-om-barnet/src/dev/devStyles.css b/packages/fakta-om-barnet/src/dev/devStyles.css new file mode 100644 index 0000000000..d18dc9c94a --- /dev/null +++ b/packages/fakta-om-barnet/src/dev/devStyles.css @@ -0,0 +1,16 @@ +@import 'tailwindcss/base'; +@import '@navikt/ds-css'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; + +.hide-legend legend { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} diff --git a/packages/fakta-om-barnet/src/styles.css b/packages/fakta-om-barnet/src/styles.css new file mode 100644 index 0000000000..4299066225 --- /dev/null +++ b/packages/fakta-om-barnet/src/styles.css @@ -0,0 +1,15 @@ +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; + +.hide-legend legend { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} diff --git a/packages/fakta-om-barnet/src/types/ContainerContract.ts b/packages/fakta-om-barnet/src/types/ContainerContract.ts new file mode 100644 index 0000000000..85f7e66aab --- /dev/null +++ b/packages/fakta-om-barnet/src/types/ContainerContract.ts @@ -0,0 +1,11 @@ +interface ContainerContract { + readOnly: boolean; + endpoints: { + rettVedDod: string; + omPleietrengende: string; + }; + httpErrorHandler: (statusCode: number, locationHeader?: string) => void; + onFinished: (vurdering) => void; +} + +export default ContainerContract; diff --git a/packages/fakta-om-barnet/src/types/Pleietrengende.ts b/packages/fakta-om-barnet/src/types/Pleietrengende.ts new file mode 100644 index 0000000000..8349d4572c --- /dev/null +++ b/packages/fakta-om-barnet/src/types/Pleietrengende.ts @@ -0,0 +1,21 @@ +import { initializeDate, prettifyDate } from '@fpsak-frontend/utils'; +import PleietrengendeResponse from './PleietrengendeResponse'; + +class Pleietrengende { + fnr: string; + + navn: string; + + diagnosekoder: string; + + dødsdato: string; + + constructor({ fnr, navn, diagnosekoder, dodsdato }: PleietrengendeResponse) { + this.fnr = fnr; + this.navn = navn; + this.diagnosekoder = diagnosekoder?.join(', '); + this.dødsdato = dodsdato ? prettifyDate(initializeDate(dodsdato)) : null; + } +} + +export default Pleietrengende; diff --git a/packages/fakta-om-barnet/src/types/PleietrengendeResponse.ts b/packages/fakta-om-barnet/src/types/PleietrengendeResponse.ts new file mode 100644 index 0000000000..74f215edd6 --- /dev/null +++ b/packages/fakta-om-barnet/src/types/PleietrengendeResponse.ts @@ -0,0 +1,8 @@ +interface PleietrengendeResponse { + fnr: string; + navn: string; + diagnosekoder: string[]; + dodsdato: string; +} + +export default PleietrengendeResponse; diff --git "a/packages/fakta-om-barnet/src/types/RettVedD\303\270d.ts" "b/packages/fakta-om-barnet/src/types/RettVedD\303\270d.ts" new file mode 100644 index 0000000000..cb3893aca6 --- /dev/null +++ "b/packages/fakta-om-barnet/src/types/RettVedD\303\270d.ts" @@ -0,0 +1,6 @@ +import RettVedDødUtfallType from './RettVedDødType'; + +export interface RettVedDød { + vurdering: string; + rettVedDødType: RettVedDødUtfallType; +} diff --git "a/packages/fakta-om-barnet/src/types/RettVedD\303\270dType.ts" "b/packages/fakta-om-barnet/src/types/RettVedD\303\270dType.ts" new file mode 100644 index 0000000000..5cef436c3d --- /dev/null +++ "b/packages/fakta-om-barnet/src/types/RettVedD\303\270dType.ts" @@ -0,0 +1,6 @@ +enum RettVedDødUtfallType { + RETT_6_UKER = 'RETT_6_UKER', + RETT_12_UKER = 'RETT_12_UKER', +} + +export default RettVedDødUtfallType; diff --git a/packages/fakta-om-barnet/src/ui/MainComponent.tsx b/packages/fakta-om-barnet/src/ui/MainComponent.tsx new file mode 100644 index 0000000000..4e642c4024 --- /dev/null +++ b/packages/fakta-om-barnet/src/ui/MainComponent.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import ContainerContract from '../types/ContainerContract'; +import ContainerContext from './context/ContainerContext'; +import OmPleietrengende from './om-pleietrengende/OmPleietrengende'; +import RettVedDødController from './rett-ved-død/rett-ved-død-controller/RettVedDødController'; +import '../styles.css'; + +interface MainComponentProps { + data: ContainerContract; +} + +const MainComponent = ({ data }: MainComponentProps): JSX.Element => ( + +

    Om barnet

    + +
    + +
    +
    +); +export default MainComponent; diff --git a/packages/fakta-om-barnet/src/ui/context/ContainerContext.ts b/packages/fakta-om-barnet/src/ui/context/ContainerContext.ts new file mode 100644 index 0000000000..b4cd425e6a --- /dev/null +++ b/packages/fakta-om-barnet/src/ui/context/ContainerContext.ts @@ -0,0 +1,5 @@ +import React from 'react'; +import ContainerContract from '../../types/ContainerContract'; + +const ContainerContext = React.createContext(null); +export default ContainerContext; diff --git a/packages/fakta-om-barnet/src/ui/om-pleietrengende/OmPleietrengende.tsx b/packages/fakta-om-barnet/src/ui/om-pleietrengende/OmPleietrengende.tsx new file mode 100644 index 0000000000..f92dc73630 --- /dev/null +++ b/packages/fakta-om-barnet/src/ui/om-pleietrengende/OmPleietrengende.tsx @@ -0,0 +1,68 @@ +import { PageContainer } from '@navikt/ft-plattform-komponenter'; +import { get } from '@fpsak-frontend/utils'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; +import Pleietrengende from '../../types/Pleietrengende'; +import PleietrengendeResponse from '../../types/PleietrengendeResponse'; +import ContainerContext from '../context/ContainerContext'; + +const OmPleietrengende = (): JSX.Element => { + const { endpoints, httpErrorHandler } = useContext(ContainerContext); + const [isLoading, setIsLoading] = useState(true); + const [hasFailed, setHasFailed] = useState(false); + const [pleietrengende, setPleietrengende] = useState(null); + const controller = useMemo(() => new AbortController(), []); + + const getOmPleietrengende = () => + get(endpoints.omPleietrengende, httpErrorHandler, { + signal: controller.signal, + }); + + useEffect(() => { + let isMounted = true; + getOmPleietrengende() + .then(response => { + if (isMounted) { + const nyPleietrengende = new Pleietrengende(response); + setPleietrengende(nyPleietrengende); + setIsLoading(false); + } + }) + .catch(() => { + setIsLoading(false); + setHasFailed(true); + isMounted = false; + controller.abort(); + }); + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + return ( + + {pleietrengende && ( +
    +

    + Navn: + {pleietrengende.navn} +

    + +

    + Fødselsnummer: + {pleietrengende.fnr} +

    + +

    + Diagnose: + {pleietrengende.diagnosekoder} +

    + {pleietrengende.dødsdato ? ( +

    {`Død ${pleietrengende.dødsdato}`}

    + ) : null} +
    + )} +
    + ); +}; +export default OmPleietrengende; diff --git "a/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-controller/RettVedD\303\270dController.tsx" "b/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-controller/RettVedD\303\270dController.tsx" new file mode 100644 index 0000000000..d621b529bc --- /dev/null +++ "b/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-controller/RettVedD\303\270dController.tsx" @@ -0,0 +1,95 @@ +import { Alert } from '@navikt/ds-react'; +import { LinkButton, PageContainer } from '@navikt/ft-plattform-komponenter'; +import { get } from '@fpsak-frontend/utils'; +import React, { useContext, useEffect, useMemo, useReducer } from 'react'; +import { RettVedDød } from '../../../types/RettVedDød'; +import ContainerContext from '../../context/ContainerContext'; +import WriteAccessBoundContent from '../../write-access-bound-content/WriteAccessBoundContent'; +import RettVedDødForm from '../rett-ved-død-form/RettVedDødForm'; +import RettVedDødVurderingsdetaljer from '../rett-ved-død-vurderingsdetaljer/RettVedDødVurderingsdetaljer'; +import ActionType from './actionTypes'; +import rettVedDødReducer from './reducer'; + +const RettVedDødController = (): JSX.Element => { + const [state, dispatch] = useReducer(rettVedDødReducer, { + hasFailed: false, + isLoading: true, + rettVedDød: null, + editMode: false, + }); + const { rettVedDød, editMode, isLoading, hasFailed } = state; + const { readOnly, endpoints, httpErrorHandler } = useContext(ContainerContext); + const controller = useMemo(() => new AbortController(), []); + + const getRettVedDød = () => + get(endpoints.rettVedDod, httpErrorHandler, { + signal: controller.signal, + }); + + useEffect(() => { + let isMounted = true; + getRettVedDød() + .then(response => { + if (isMounted) { + dispatch({ type: ActionType.OK, rettVedDød: response }); + } + }) + .catch(() => { + dispatch({ type: ActionType.FAILED }); + isMounted = false; + controller.abort(); + }); + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + const getContent = () => { + const getHeading = () =>

    Rett til pleiepenger ved barnets død

    ; + + if (rettVedDød && (!editMode || readOnly)) { + return ( + <> +
    + {getHeading()} + ( + dispatch({ type: ActionType.ENABLE_EDIT })}> + Rediger vurdering + + )} + /> +
    + + + ); + } + + return ( + <> + + Kontroller om søker har søkt om pleiepenger for en periode som varer minst seks uker etter barnets dødsdato. + Dersom det ikke er gjort, se unntaksrutinen ved barns død. + + {getHeading()} +
    + Vurder hvor lang periode søker har rett på pleiepenger ved barnets død. + dispatch({ type: ActionType.ABORT_EDIT })} /> +
    + + ); + }; + + if (readOnly && !rettVedDød) { + return null; + } + + return ( + + {getContent()} + + ); +}; + +export default RettVedDødController; diff --git "a/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-controller/actionTypes.ts" "b/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-controller/actionTypes.ts" new file mode 100644 index 0000000000..7ca4a5b7de --- /dev/null +++ "b/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-controller/actionTypes.ts" @@ -0,0 +1,8 @@ +enum ActionType { + OK = 'OK', + FAILED = 'FAILED', + ENABLE_EDIT = 'ENABLE_EDIT', + ABORT_EDIT = 'ABORT_EDIT', +} + +export default ActionType; diff --git "a/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-controller/reducer.ts" "b/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-controller/reducer.ts" new file mode 100644 index 0000000000..1498a53382 --- /dev/null +++ "b/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-controller/reducer.ts" @@ -0,0 +1,45 @@ +import { RettVedDød } from '../../../types/RettVedDød'; +import ActionType from './actionTypes'; + +interface MainComponentState { + rettVedDød: RettVedDød; + hasFailed: boolean; + isLoading: boolean; + editMode: boolean; +} + +interface Action { + type: ActionType; + rettVedDød?: RettVedDød; +} + +const rettVedDødReducer = (state: MainComponentState, action: Action): Partial => { + switch (action.type) { + case ActionType.OK: + return { + rettVedDød: action.rettVedDød, + hasFailed: false, + isLoading: false, + }; + case ActionType.FAILED: + return { + rettVedDød: null, + hasFailed: true, + isLoading: false, + }; + case ActionType.ENABLE_EDIT: + return { + ...state, + editMode: true, + }; + case ActionType.ABORT_EDIT: + return { + ...state, + editMode: false, + }; + default: + return { ...state }; + } +}; + +export default rettVedDødReducer; diff --git "a/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-form/RettVedD\303\270dForm.tsx" "b/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-form/RettVedD\303\270dForm.tsx" new file mode 100644 index 0000000000..1a44e0896b --- /dev/null +++ "b/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-form/RettVedD\303\270dForm.tsx" @@ -0,0 +1,85 @@ +import React, { useContext } from 'react'; +import { useForm, FormProvider } from 'react-hook-form'; +import { Form } from '@navikt/ft-plattform-komponenter'; +import { RadioGroupPanelRHF, TextAreaRHF } from '@fpsak-frontend/form'; +import required from '../../../validators/required'; +import ContainerContext from '../../context/ContainerContext'; +import RettVedDødUtfallType from '../../../types/RettVedDødType'; +import { RettVedDød } from '../../../types/RettVedDød'; + +export enum FieldName { + RETT_VED_DØD_TYPE = 'rettVedDødType', + VURDERING = 'vurdering', +} + +interface RettVedDødFormState { + [FieldName.VURDERING]: string; + [FieldName.RETT_VED_DØD_TYPE]: RettVedDødUtfallType; +} + +interface RettVedDødFormProps { + rettVedDød?: RettVedDød; + onCancelClick: () => void; +} + +const RettVedDødForm = ({ rettVedDød, onCancelClick }: RettVedDødFormProps): JSX.Element => { + const formMethods = useForm({ + defaultValues: { + [FieldName.VURDERING]: rettVedDød?.vurdering, + [FieldName.RETT_VED_DØD_TYPE]: rettVedDød?.rettVedDødType, + }, + }); + const { readOnly, onFinished } = useContext(ContainerContext); + const [isSubmitting, setIsSubmitting] = React.useState(false); + + const handleSubmit = (formState: RettVedDødFormState) => { + setIsSubmitting(true); + setTimeout(() => { + setIsSubmitting(false); + }, 2.5 * 1000); + const { vurdering, rettVedDødType } = formState; + onFinished({ vurdering, rettVedDødType }); + }; + + return ( +
    + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + +
    +
    + +
    +
    + +
    +
    +
    +
    + ); +}; + +export default RettVedDødForm; diff --git "a/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-vurderingsdetaljer/RettVedD\303\270dVurderingsdetaljer.tsx" "b/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-vurderingsdetaljer/RettVedD\303\270dVurderingsdetaljer.tsx" new file mode 100644 index 0000000000..d34256957e --- /dev/null +++ "b/packages/fakta-om-barnet/src/ui/rett-ved-d\303\270d/rett-ved-d\303\270d-vurderingsdetaljer/RettVedD\303\270dVurderingsdetaljer.tsx" @@ -0,0 +1,33 @@ +import { GreenCheckIcon } from '@navikt/ft-plattform-komponenter'; +import React from 'react'; +import { RettVedDød } from '../../../types/RettVedDød'; +import RettVedDødUtfallType from '../../../types/RettVedDødType'; + +interface RettVedDødVurderingsdetaljerProps { + rettVedDød: RettVedDød; +} + +const RettVedDødVurderingsdetaljer = ({ rettVedDød }: RettVedDødVurderingsdetaljerProps): JSX.Element => { + const getRettVedDødUtfallTekst = () => { + if (rettVedDød.rettVedDødType === RettVedDødUtfallType.RETT_6_UKER) { + return 'Søker har mottatt pleiepenger i mindre enn 3 år og har rett på 30 dager (6 uker) med pleiepenger jf § 9-10, fjerde ledd, første punktum.'; + } + + return 'Søker har mottatt 100 % pleiepenger i 3 år eller mer og har rett på 3 måneder med pleiepenger jf § 9-10, fjerde ledd, andre punktum.'; + }; + + return ( + <> +
    + +

    {getRettVedDødUtfallTekst()}

    +
    +
    +

    Vurdering

    +

    {rettVedDød.vurdering}

    +
    + + ); +}; + +export default RettVedDødVurderingsdetaljer; diff --git a/packages/fakta-om-barnet/src/ui/write-access-bound-content/WriteAccessBoundContent.tsx b/packages/fakta-om-barnet/src/ui/write-access-bound-content/WriteAccessBoundContent.tsx new file mode 100644 index 0000000000..323bdd835c --- /dev/null +++ b/packages/fakta-om-barnet/src/ui/write-access-bound-content/WriteAccessBoundContent.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import ContainerContext from '../context/ContainerContext'; + +interface WriteAccessBoundContentProps { + contentRenderer: () => JSX.Element; + otherRequirementsAreMet?: boolean; +} + +const WriteAccessBoundContent = ({ + contentRenderer, + otherRequirementsAreMet, +}: WriteAccessBoundContentProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + if (readOnly === false && (otherRequirementsAreMet === true || otherRequirementsAreMet === undefined)) { + return contentRenderer(); + } + return null; +}; + +export default WriteAccessBoundContent; diff --git a/packages/fakta-om-barnet/src/util/renderers.tsx b/packages/fakta-om-barnet/src/util/renderers.tsx new file mode 100644 index 0000000000..2f24350b27 --- /dev/null +++ b/packages/fakta-om-barnet/src/util/renderers.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import MainComponent from '../ui/MainComponent'; +import ContainerContract from '../types/ContainerContract'; + +function prepare() { + if (process.env.NODE_ENV !== 'production') { + return import('../../mock/browser').then(({ worker }) => worker.start({ onUnhandledRequest: 'bypass' })); + } + + return Promise.resolve(); +} + +const renderAppInSuccessfulState = (appId: string, data: ContainerContract): Promise => + prepare().then(() => { + const container = document.getElementById(appId); + const root = createRoot(container); + root.render(); + }); + +export default { + renderAppInSuccessfulState, +}; diff --git a/packages/fakta-om-barnet/src/validators/__tests__/validators.spec.ts b/packages/fakta-om-barnet/src/validators/__tests__/validators.spec.ts new file mode 100644 index 0000000000..973cdc463f --- /dev/null +++ b/packages/fakta-om-barnet/src/validators/__tests__/validators.spec.ts @@ -0,0 +1,10 @@ +import required from '../required'; + +test('required', () => { + const feilmelding = 'Du må oppgi en verdi'; + + expect(required('dette skal fungere')).toBe(true); + expect(required(null)).toBe(feilmelding); + expect(required(undefined)).toBe(feilmelding); + expect(required('')).toBe(feilmelding); +}); diff --git a/packages/fakta-om-barnet/src/validators/required.ts b/packages/fakta-om-barnet/src/validators/required.ts new file mode 100644 index 0000000000..44697ad3d9 --- /dev/null +++ b/packages/fakta-om-barnet/src/validators/required.ts @@ -0,0 +1,6 @@ +export default function required(v: string | number): string | boolean { + if (v === null || v === undefined || v === '') { + return 'Du må oppgi en verdi'; + } + return true; +} diff --git a/packages/fakta-om-barnet/tailwind.config.js b/packages/fakta-om-barnet/tailwind.config.js new file mode 100644 index 0000000000..067a6b26ea --- /dev/null +++ b/packages/fakta-om-barnet/tailwind.config.js @@ -0,0 +1,17 @@ +/* eslint-disable global-require */ +module.exports = { + content: ['./src/**/*.{ts,tsx}'], + presets: [require('@navikt/ds-tailwind')], + theme: { + extend: { + colors: { 'warning-yellow': '#ff9100' }, + }, + fontFamily: { + sans: ['Source Sans Pro', 'Arial', 'sans-serif'], + }, + }, + plugins: [], + corePlugins: { + preflight: false, + }, +}; diff --git a/packages/fakta-om-barnet/webpack/webpack-config.development.js b/packages/fakta-om-barnet/webpack/webpack-config.development.js new file mode 100644 index 0000000000..151f65b99d --- /dev/null +++ b/packages/fakta-om-barnet/webpack/webpack-config.development.js @@ -0,0 +1,40 @@ +/* eslint-disable import/extensions */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const webpack = require('webpack'); +const path = require('path'); +const { merge } = require('webpack-merge'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const WebpackDevServer = require('webpack-dev-server'); +const commonWebpackConfig = require('./webpack.common.js'); + +const webpackConfig = merge(commonWebpackConfig, { + entry: `${path.resolve(__dirname, '../', 'src')}/dev/app.ts`, + mode: 'development', + devtool: 'eval-cheap-module-source-map', + plugins: [ + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, '../index.html'), + }), + ], +}); + +const port = 8484; +const devServerOptions = { + hot: true, + headers: { + 'Access-Control-Allow-Origin': 'http://localhost:9000', + }, + port, +}; + +const compiler = webpack(webpackConfig); +const devServer = new WebpackDevServer(devServerOptions, compiler); +compiler.close(() => console.info('Compiler closed')); + +devServer.startCallback(error => { + if (error) { + console.error(error); + } else { + console.log(`Listening at port ${port}`); + } +}); diff --git a/packages/fakta-om-barnet/webpack/webpack.common.js b/packages/fakta-om-barnet/webpack/webpack.common.js new file mode 100644 index 0000000000..a0bcbcfd3f --- /dev/null +++ b/packages/fakta-om-barnet/webpack/webpack.common.js @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const process = require('process'); +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const cssExtractLoaderConfig = { + loader: MiniCssExtractPlugin.loader, +}; + +const nodeModules = path.resolve(__dirname, '../node_modules'); +const rootNodeModules = path.resolve(__dirname, '../../../node_modules'); + +module.exports = { + resolve: { + extensions: ['.ts', '.tsx', '.js', '.css'], + }, + module: { + rules: [ + { + test: /\.(ts|js)x?$/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + rootMode: 'upward', + }, + }, + }, + { + test: /\.css$/, + use: [ + cssExtractLoaderConfig, + { + loader: 'css-loader', + options: { + importLoaders: 1, + modules: { + localIdentName: '[name]_[local]_[contenthash:base64:5]', + }, + }, + }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [ + ['tailwindcss', {}], + ['autoprefixer', {}], + ], + }, + }, + }, + ], + exclude: [nodeModules, rootNodeModules], + }, + { + test: /\.(less|css)?$/, + use: [ + cssExtractLoaderConfig, + { + loader: 'css-loader', + }, + { + loader: 'less-loader', + options: { + lessOptions: { + modifyVars: { + nodeModulesPath: '~', + coreModulePath: '~', + }, + }, + }, + }, + ], + include: [nodeModules, rootNodeModules], + }, + { + test: /\.(jpg|png|svg)$/, + loader: 'file-loader', + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: 'styles.css', + }), + ], +}; diff --git a/packages/fakta-omsorgen-for/index.html b/packages/fakta-omsorgen-for/index.html new file mode 100644 index 0000000000..0a56017f6c --- /dev/null +++ b/packages/fakta-omsorgen-for/index.html @@ -0,0 +1,26 @@ + + + Omsorgen for + + + +
    + + + diff --git a/packages/fakta-omsorgen-for/index.ts b/packages/fakta-omsorgen-for/index.ts new file mode 100644 index 0000000000..1fac2e4662 --- /dev/null +++ b/packages/fakta-omsorgen-for/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as OmsorgenFor } from './src/ui/MainComponent'; diff --git a/packages/fakta-omsorgen-for/mock/api-mock.ts b/packages/fakta-omsorgen-for/mock/api-mock.ts new file mode 100644 index 0000000000..c465b51ff3 --- /dev/null +++ b/packages/fakta-omsorgen-for/mock/api-mock.ts @@ -0,0 +1,17 @@ +/* eslint-disable import/prefer-default-export */ +import { rest } from 'msw'; +import mockedOmsorgsperioder from './mocked-data/mockedOmsorgsperioder'; + +export const handlers = [ + rest.get('http://localhost:8082/mock/omsorgsperioder', (req, res, ctx) => + res( + ctx.status(200), + ctx.json({ + omsorgsperioder: mockedOmsorgsperioder, + registrertSammeBosted: true, + registrertForeldrerelasjon: true, + tvingManuellVurdering: false, + }), + ), + ), +]; diff --git a/packages/fakta-omsorgen-for/mock/browser.ts b/packages/fakta-omsorgen-for/mock/browser.ts new file mode 100644 index 0000000000..4ac10c54c5 --- /dev/null +++ b/packages/fakta-omsorgen-for/mock/browser.ts @@ -0,0 +1,6 @@ +/* eslint-disable import/prefer-default-export */ +/* eslint-disable import/no-extraneous-dependencies */ +import { setupWorker } from 'msw'; +import { handlers } from './api-mock'; +// This configures a Service Worker with the given request handlers. +export const worker = setupWorker(...handlers); diff --git a/packages/fakta-omsorgen-for/mock/mocked-data/mockedOmsorgsperioder.ts b/packages/fakta-omsorgen-for/mock/mocked-data/mockedOmsorgsperioder.ts new file mode 100644 index 0000000000..e5c6f663dd --- /dev/null +++ b/packages/fakta-omsorgen-for/mock/mocked-data/mockedOmsorgsperioder.ts @@ -0,0 +1,43 @@ +import Vurderingsresultat from '../../src/types/Vurderingsresultat'; + +const omsorgsperioder = [ + { + begrunnelse: null, + periode: { fom: '2021-03-20', tom: '2021-03-25' } as any, + relasjon: 'Annet', + relasjonsbeskrivelse: 'Nabo', + resultat: Vurderingsresultat.IKKE_VURDERT, + resultatEtterAutomatikk: Vurderingsresultat.IKKE_VURDERT, + }, + { + begrunnelse: 'Fordi foo og bar', + periode: { fom: '2021-03-16', tom: '2021-03-20' } as any, + relasjon: 'Annet', + relasjonsbeskrivelse: 'Nabo', + resultat: Vurderingsresultat.IKKE_OPPFYLT, + resultatEtterAutomatikk: Vurderingsresultat.IKKE_VURDERT, + }, + { + periode: { fom: '2021-03-09', tom: '2021-03-15' } as any, + resultat: Vurderingsresultat.IKKE_VURDERT, + resultatEtterAutomatikk: Vurderingsresultat.OPPFYLT, + }, + { + begrunnelse: 'Fordi ditt og datt', + periode: { fom: '2021-03-01', tom: '2021-03-05' } as any, + relasjon: 'Far', + relasjonsbeskrivelse: null, + resultat: Vurderingsresultat.OPPFYLT, + resultatEtterAutomatikk: Vurderingsresultat.OPPFYLT, + }, + { + begrunnelse: 'Fordi sånn og sånn', + periode: { fom: '2021-02-01', tom: '2021-02-05' } as any, + relasjon: 'Far', + relasjonsbeskrivelse: null, + resultat: Vurderingsresultat.OPPFYLT, + resultatEtterAutomatikk: Vurderingsresultat.OPPFYLT, + }, +]; + +export default omsorgsperioder; diff --git a/packages/fakta-omsorgen-for/package.json b/packages/fakta-omsorgen-for/package.json new file mode 100644 index 0000000000..ba5eabca9c --- /dev/null +++ b/packages/fakta-omsorgen-for/package.json @@ -0,0 +1,54 @@ +{ + "name": "@k9-sak-web/fakta-omsorgen-for", + "version": "1.0.0", + "module": "index.ts", + "keywords": [], + "author": "NAV IT", + "license": "MIT", + "private": true, + "scripts": { + "dev": "node webpack/webpack-config.development.js" + }, + "dependencies": { + "@navikt/ds-css": "5.11.2", + "@navikt/ds-icons": "3.4.3", + "@navikt/ds-react": "5.11.2", + "@navikt/fnrvalidator": "1.3.3", + "@navikt/ft-plattform-komponenter": "2.3.14", + "@popperjs/core": "2.11.8", + "axios": "1.6.2", + "classnames": "2.3.2", + "dayjs": "1.11.10", + "lodash.throttle": "4.1.1", + "nav-frontend-alertstriper": "4.0.2", + "nav-frontend-alertstriper-style": "3.0.2", + "nav-frontend-chevron": "1.0.30", + "nav-frontend-chevron-style": "1.0.4", + "nav-frontend-core": "6.0.1", + "nav-frontend-ikoner-assets": "3.0.1", + "nav-frontend-ikonknapper": "2.1.3", + "nav-frontend-js-utils": "1.0.20", + "nav-frontend-knapper": "3.1.3", + "nav-frontend-knapper-style": "2.1.2", + "nav-frontend-lenker": "2.0.2", + "nav-frontend-lenker-style": "2.0.2", + "nav-frontend-paneler": "3.0.2", + "nav-frontend-paneler-style": "2.0.2", + "nav-frontend-skjema": "4.0.6", + "nav-frontend-skjema-style": "3.0.3", + "nav-frontend-spinner": "3.0.1", + "nav-frontend-spinner-style": "1.0.2", + "nav-frontend-typografi": "4.0.2", + "nav-frontend-typografi-style": "2.0.2", + "object-hash": "3.0.0", + "react": "18.2.0", + "react-collapse": "5.1.1", + "react-dom": "18.2.0", + "react-hook-form": "7.48.2", + "react-intl": "6.5.5", + "react-popper": "2.3.0" + }, + "msw": { + "workerDirectory": "public" + } +} diff --git a/packages/fakta-omsorgen-for/public/mockServiceWorker.js b/packages/fakta-omsorgen-for/public/mockServiceWorker.js new file mode 100644 index 0000000000..95835ef353 --- /dev/null +++ b/packages/fakta-omsorgen-for/public/mockServiceWorker.js @@ -0,0 +1,302 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (1.3.2). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'; +const activeClientIds = new Set(); + +self.addEventListener('install', function () { + self.skipWaiting(); +}); + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener('message', async function (event) { + const clientId = event.source.id; + + if (!clientId || !self.clients) { + return; + } + + const client = await self.clients.get(clientId); + + if (!client) { + return; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }); + break; + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }); + break; + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId); + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }); + break; + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId); + break; + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId); + + const remainingClients = allClients.filter(client => { + return client.id !== clientId; + }); + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister(); + } + + break; + } + } +}); + +self.addEventListener('fetch', function (event) { + const { request } = event; + const accept = request.headers.get('accept') || ''; + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return; + } + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return; + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return; + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return; + } + + // Generate unique request ID. + const requestId = Math.random().toString(16).slice(2); + + event.respondWith( + handleRequest(event, requestId).catch(error => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url, + ); + return; + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}`, + ); + }), + ); +}); + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event); + const response = await getResponse(event, client, requestId); + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + (async function () { + const clonedResponse = response.clone(); + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: clonedResponse.body === null ? null : await clonedResponse.text(), + headers: Object.fromEntries(clonedResponse.headers.entries()), + redirected: clonedResponse.redirected, + }, + }); + })(); + } + + return response; +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId); + + if (client?.frameType === 'top-level') { + return client; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + return allClients + .filter(client => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible'; + }) + .find(client => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id); + }); +} + +async function getResponse(event, client, requestId) { + const { request } = event; + const clonedRequest = request.clone(); + + function passthrough() { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const headers = Object.fromEntries(clonedRequest.headers.entries()); + + // Remove MSW-specific request headers so the bypassed requests + // comply with the server's CORS preflight check. + // Operate with the headers as an object because request "Headers" + // are immutable. + delete headers['x-msw-bypass']; + + return fetch(clonedRequest, { headers }); + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough(); + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough(); + } + + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough(); + } + + // Notify the client that a request has been intercepted. + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.text(), + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }); + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data); + } + + case 'MOCK_NOT_FOUND': { + return passthrough(); + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.data; + const networkError = new Error(message); + networkError.name = name; + + // Rejecting a "respondWith" promise emulates a network error. + throw networkError; + } + } + + return passthrough(); +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel(); + + channel.port1.onmessage = event => { + if (event.data && event.data.error) { + return reject(event.data.error); + } + + resolve(event.data); + }; + + client.postMessage(message, [channel.port2]); + }); +} + +function sleep(timeMs) { + return new Promise(resolve => { + setTimeout(resolve, timeMs); + }); +} + +async function respondWithMock(response) { + await sleep(response.delay); + return new Response(response.body, response); +} diff --git a/packages/fakta-omsorgen-for/src/constants/dateConstants.ts b/packages/fakta-omsorgen-for/src/constants/dateConstants.ts new file mode 100644 index 0000000000..1cdcbb3994 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/constants/dateConstants.ts @@ -0,0 +1,9 @@ +import dayjs from 'dayjs'; + +export const today = dayjs().utc(true).startOf('day'); +export const tomorrow = today.add(1, 'day').startOf('day'); + +export default { + today, + tomorrow, +}; diff --git a/packages/fakta-omsorgen-for/src/dev/app.ts b/packages/fakta-omsorgen-for/src/dev/app.ts new file mode 100644 index 0000000000..a8a555d772 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/dev/app.ts @@ -0,0 +1,13 @@ +import { ContainerContract } from '../types/ContainerContract'; +import renderers from '../util/renderers'; +import '@navikt/ft-plattform-komponenter/dist/style.css'; +import '@navikt/ds-css'; + +interface ExtendedWindow extends Window { + renderOmsorgenForApp: (id: string, contract: ContainerContract) => void; +} + +(window as Partial).renderOmsorgenForApp = async (appId, data) => { + const { renderAppInSuccessfulState } = renderers; + renderAppInSuccessfulState(appId, data); +}; diff --git a/packages/fakta-omsorgen-for/src/nb_NO.js b/packages/fakta-omsorgen-for/src/nb_NO.js new file mode 100644 index 0000000000..0cbd3eec3b --- /dev/null +++ b/packages/fakta-omsorgen-for/src/nb_NO.js @@ -0,0 +1,19 @@ +export const felles = {}; + +export const omsorgspenger = { + ...felles, + 'vurdering.tittel': 'Vurdering', + 'vurdering.harOmsorgenFor': 'Er vilkåret oppfylt for denne perioden?', + 'vurdering.hjemmel': 'Vurder om søker har omsorg for barn etter § 9-5.', + 'vurdering.hjemmel.hjelpetekst': + 'Hvis søker ikke oppfyller vilkåret etter § 9-5, så skal vilkåret likevel settes oppfylt dersom søker kan ha fått fordelt eller overført dager etter § 9-6, femte og sjette ledd', + 'vurdering.advarsel': 'Vurder om søker har omsorgen for barn i perioden.', +}; + +export const pleiepenger = { + ...felles, + 'vurdering.tittel': 'Vurdering av omsorg', + 'vurdering.harOmsorgenFor': 'Har søker omsorgen for barnet i denne perioden?', + 'vurdering.hjemmel': 'Vurder om søker har omsorgen for barnet etter § 9-10, første ledd.', + 'vurdering.advarsel': 'Vurder om søker har omsorgen for barnet i {perioder}.', +}; diff --git a/packages/fakta-omsorgen-for/src/types/ContainerContract.ts b/packages/fakta-omsorgen-for/src/types/ContainerContract.ts new file mode 100644 index 0000000000..39c8035d6d --- /dev/null +++ b/packages/fakta-omsorgen-for/src/types/ContainerContract.ts @@ -0,0 +1,10 @@ +export interface ContainerContract { + endpoints: { + omsorgsperioder: string; + }; + readOnly: boolean; + onFinished: (vurdering, fosterbarnForOmsorgspenger) => void; + httpErrorHandler: (statusCode: number, locationHeader?: string) => void; + sakstype?: string; + saksbehandlere: { [key: string]: string }; +} diff --git a/packages/fakta-omsorgen-for/src/types/Omsorgsperiode.ts b/packages/fakta-omsorgen-for/src/types/Omsorgsperiode.ts new file mode 100644 index 0000000000..b76107355b --- /dev/null +++ b/packages/fakta-omsorgen-for/src/types/Omsorgsperiode.ts @@ -0,0 +1,85 @@ +import { Period } from '@fpsak-frontend/utils'; +import Vurderingsresultat from './Vurderingsresultat'; + +class Omsorgsperiode { + periode: Period; + + relasjon: string; + + relasjonsbeskrivelse?: string; + + resultat: Vurderingsresultat; + + resultatEtterAutomatikk: Vurderingsresultat; + + begrunnelse?: string; + + vurdertAv?: string; + + vurdertTidspunkt?: string; + + constructor({ + periode, + resultatEtterAutomatikk, + resultat, + relasjonsbeskrivelse, + begrunnelse, + relasjon, + vurdertAv, + vurdertTidspunkt, + }: Partial) { + this.periode = new Period(periode.fom, periode.tom); + this.resultatEtterAutomatikk = resultatEtterAutomatikk; + this.resultat = resultat; + this.relasjonsbeskrivelse = relasjonsbeskrivelse; + this.begrunnelse = begrunnelse; + this.relasjon = relasjon ? relasjon[0].toUpperCase() + relasjon.slice(1).toLowerCase() : ''; + this.vurdertAv = vurdertAv; + this.vurdertTidspunkt = vurdertTidspunkt; + } + + erOppfylt(): boolean { + return this.resultat === Vurderingsresultat.OPPFYLT || this.resultatEtterAutomatikk === Vurderingsresultat.OPPFYLT; + } + + erIkkeOppfylt(): boolean { + return ( + this.resultat === Vurderingsresultat.IKKE_OPPFYLT || + this.resultatEtterAutomatikk === Vurderingsresultat.IKKE_OPPFYLT + ); + } + + erAutomatiskVurdert(): boolean { + return ( + this.resultatEtterAutomatikk === Vurderingsresultat.OPPFYLT || + this.resultatEtterAutomatikk === Vurderingsresultat.IKKE_OPPFYLT + ); + } + + erManueltVurdert(): boolean { + return this.resultat === Vurderingsresultat.OPPFYLT || this.resultat === Vurderingsresultat.IKKE_OPPFYLT; + } + + erVurdert(): boolean { + return this.erManueltVurdert() || this.erAutomatiskVurdert(); + } + + manglerVurdering(): boolean { + return ( + this.resultat === Vurderingsresultat.IKKE_VURDERT && + this.resultatEtterAutomatikk === Vurderingsresultat.IKKE_VURDERT + ); + } + + hentResultat(): Vurderingsresultat { + if (this.resultat === Vurderingsresultat.IKKE_VURDERT) { + return this.resultatEtterAutomatikk; + } + if (this.resultatEtterAutomatikk === Vurderingsresultat.IKKE_VURDERT) { + return this.resultat; + } + return this.resultat || this.resultatEtterAutomatikk; + } +} + +export default Omsorgsperiode; diff --git a/packages/fakta-omsorgen-for/src/types/Omsorgsperiodeoversikt.ts b/packages/fakta-omsorgen-for/src/types/Omsorgsperiodeoversikt.ts new file mode 100644 index 0000000000..44b19c4325 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/types/Omsorgsperiodeoversikt.ts @@ -0,0 +1,38 @@ +import Omsorgsperiode from './Omsorgsperiode'; +import OmsorgsperioderResponse from './OmsorgsperioderResponse'; + +class Omsorgsperiodeoversikt { + perioder: Omsorgsperiode[]; + + registrertSammeBosted: boolean; + + registrertForeldrerelasjon: boolean; + + tvingManuellVurdering: boolean; + + constructor({ + tvingManuellVurdering, + omsorgsperioder, + registrertForeldrerelasjon, + registrertSammeBosted, + }: OmsorgsperioderResponse) { + this.perioder = omsorgsperioder.map(omsorgsperiode => new Omsorgsperiode(omsorgsperiode)); + this.tvingManuellVurdering = tvingManuellVurdering; + this.registrertForeldrerelasjon = registrertForeldrerelasjon; + this.registrertSammeBosted = registrertSammeBosted; + } + + harPerioderTilVurdering(): boolean { + return this.perioder.some(omsorgspeiode => omsorgspeiode.manglerVurdering()); + } + + finnVurdertePerioder(): Omsorgsperiode[] { + return this.perioder.filter(omsorgsperiode => omsorgsperiode.erVurdert()); + } + + finnPerioderTilVurdering(): Omsorgsperiode[] { + return this.perioder.filter(omsorgsperiode => omsorgsperiode.manglerVurdering()); + } +} + +export default Omsorgsperiodeoversikt; diff --git a/packages/fakta-omsorgen-for/src/types/OmsorgsperioderResponse.ts b/packages/fakta-omsorgen-for/src/types/OmsorgsperioderResponse.ts new file mode 100644 index 0000000000..64473ef26a --- /dev/null +++ b/packages/fakta-omsorgen-for/src/types/OmsorgsperioderResponse.ts @@ -0,0 +1,10 @@ +import Omsorgsperiode from './Omsorgsperiode'; + +interface OmsorgsperioderResponse { + omsorgsperioder: Omsorgsperiode[]; + registrertSammeBosted: boolean; + registrertForeldrerelasjon: boolean; + tvingManuellVurdering: boolean; +} + +export default OmsorgsperioderResponse; diff --git a/packages/fakta-omsorgen-for/src/types/Relasjon.ts b/packages/fakta-omsorgen-for/src/types/Relasjon.ts new file mode 100644 index 0000000000..ad9f03cdd3 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/types/Relasjon.ts @@ -0,0 +1,9 @@ +enum Relajson { + MOR = 'Mor', + FAR = 'Far', + MEDMOR = 'Medmor', + FOSTERFORELDER = 'Fosterforelder', + ANNET = 'Annet', +} + +export default Relajson; diff --git a/packages/fakta-omsorgen-for/src/types/Vurderingsresultat.ts b/packages/fakta-omsorgen-for/src/types/Vurderingsresultat.ts new file mode 100644 index 0000000000..72915f2048 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/types/Vurderingsresultat.ts @@ -0,0 +1,7 @@ +enum Vurderingsresultat { + OPPFYLT = 'OPPFYLT', + IKKE_OPPFYLT = 'IKKE_OPPFYLT', + IKKE_VURDERT = 'IKKE_VURDERT', +} + +export default Vurderingsresultat; diff --git a/packages/fakta-omsorgen-for/src/types/Ytelsestype.ts b/packages/fakta-omsorgen-for/src/types/Ytelsestype.ts new file mode 100644 index 0000000000..64a3026da9 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/types/Ytelsestype.ts @@ -0,0 +1,6 @@ +enum Ytelsestype { + PSB = 'PSB', + OMP = 'OMP', +} + +export default Ytelsestype; diff --git a/packages/fakta-omsorgen-for/src/ui/MainComponent.tsx b/packages/fakta-omsorgen-for/src/ui/MainComponent.tsx new file mode 100644 index 0000000000..462a49b050 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/MainComponent.tsx @@ -0,0 +1,73 @@ +import { Box, Margin, PageContainer } from '@navikt/ft-plattform-komponenter'; +import { get } from '@fpsak-frontend/utils'; +import React from 'react'; +import { IntlProvider } from 'react-intl'; +import { ContainerContract } from '../types/ContainerContract'; +import OmsorgsperiodeoversiktType from '../types/Omsorgsperiodeoversikt'; +import OmsorgsperioderResponse from '../types/OmsorgsperioderResponse'; +import Ytelsestype from '../types/Ytelsestype'; +import { teksterForSakstype } from '../util/utils'; +import ActionType from './actionTypes'; +import Omsorgsperiodeoversikt from './components/omsorgsperiodeoversikt/Omsorgsperiodeoversikt'; +import ContainerContext from './context/ContainerContext'; +import styles from './mainComponent.css'; +import mainComponentReducer from './reducer'; + +interface MainComponentProps { + data: ContainerContract; +} + +const MainComponent = ({ data }: MainComponentProps): JSX.Element => { + const { sakstype } = data; + const [state, dispatch] = React.useReducer(mainComponentReducer, { + isLoading: true, + omsorgsperiodeoversiktHarFeilet: false, + omsorgsperiodeoversikt: null, + }); + + const { omsorgsperiodeoversikt, isLoading, omsorgsperiodeoversiktHarFeilet } = state; + + const controller = React.useMemo(() => new AbortController(), []); + + const getOmsorgsperioder = () => + get(data.endpoints.omsorgsperioder, data.httpErrorHandler, { + signal: controller.signal, + }); + + const handleError = () => { + dispatch({ type: ActionType.FAILED }); + }; + + React.useEffect(() => { + let isMounted = true; + getOmsorgsperioder() + .then((response: OmsorgsperioderResponse) => { + if (isMounted) { + const nyOmsorgsperiodeoversikt = new OmsorgsperiodeoversiktType(response); + dispatch({ type: ActionType.OK, omsorgsperiodeoversikt: nyOmsorgsperiodeoversikt }); + } + }) + .catch(handleError); + return () => { + isMounted = false; + controller.abort(); + }; + }, []); + + return ( + + +

    {sakstype === Ytelsestype.OMP ? 'Omsorgen for' : 'Omsorg'}

    + + +
    + +
    +
    +
    +
    +
    + ); +}; + +export default MainComponent; diff --git a/packages/fakta-omsorgen-for/src/ui/actionTypes.ts b/packages/fakta-omsorgen-for/src/ui/actionTypes.ts new file mode 100644 index 0000000000..fe47614b80 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/actionTypes.ts @@ -0,0 +1,7 @@ +enum ActionType { + OK = 'OK', + FAILED = 'FAILED', + PENDING = 'PENDING', +} + +export default ActionType; diff --git a/packages/fakta-omsorgen-for/src/ui/components/add-button/AddButton.tsx b/packages/fakta-omsorgen-for/src/ui/components/add-button/AddButton.tsx new file mode 100644 index 0000000000..af85a24f9b --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/add-button/AddButton.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { PlusIcon } from '@navikt/ft-plattform-komponenter'; +import styles from './addButton.css'; + +interface AddButtonProps { + onClick: () => void; + label: string; + id?: string; + className?: string; +} + +const AddButton = ({ className, label, onClick, id }: AddButtonProps): JSX.Element => ( + +); + +export default AddButton; diff --git a/packages/fakta-omsorgen-for/src/ui/components/add-button/addButton.css b/packages/fakta-omsorgen-for/src/ui/components/add-button/addButton.css new file mode 100644 index 0000000000..8eb836880f --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/add-button/addButton.css @@ -0,0 +1,15 @@ +.addButton { + display: flex; + align-items: center; + padding: 0; + color: #0067c5; + border: none; + background: none; + height: 23px; + position: relative; + cursor: pointer; +} + +.addButton__text { + margin-left: 0.5rem; +} diff --git a/packages/fakta-omsorgen-for/src/ui/components/delete-button/DeleteButton.tsx b/packages/fakta-omsorgen-for/src/ui/components/delete-button/DeleteButton.tsx new file mode 100644 index 0000000000..65fde20153 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/delete-button/DeleteButton.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { BucketIcon } from '@navikt/ft-plattform-komponenter'; +import styles from './deleteButton.css'; + +interface DeleteButtonProps { + onClick: () => void; +} + +const DeleteButton = ({ onClick }: DeleteButtonProps): JSX.Element => ( +
    + +
    +); + +export default DeleteButton; diff --git a/packages/fakta-omsorgen-for/src/ui/components/delete-button/deleteButton.css b/packages/fakta-omsorgen-for/src/ui/components/delete-button/deleteButton.css new file mode 100644 index 0000000000..201484ca4e --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/delete-button/deleteButton.css @@ -0,0 +1,13 @@ +.deleteButton__container { + margin-left: 1rem; + margin-bottom: 0.5rem; + align-self: flex-end; +} + +.deleteButton__button { + padding: 0; + border: none; + background: none; + height: 23px; + cursor: pointer; +} diff --git a/packages/fakta-omsorgen-for/src/ui/components/fosterbarn/Fosterbarn.tsx b/packages/fakta-omsorgen-for/src/ui/components/fosterbarn/Fosterbarn.tsx new file mode 100644 index 0000000000..438006c820 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/fosterbarn/Fosterbarn.tsx @@ -0,0 +1,106 @@ +/* eslint-disable react/jsx-props-no-spreading */ +import { Delete } from '@navikt/ds-icons'; +import { Button, Heading, Panel, Table, TextField } from '@navikt/ds-react'; +import validator from '@navikt/fnrvalidator'; +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import React, { useContext, useEffect } from 'react'; +import { useFieldArray, useForm, useWatch } from 'react-hook-form'; +import ContainerContext from '../../context/ContainerContext'; + +interface FosterbarnProps { + setFosterbarn: React.Dispatch>; +} + +const Fosterbarn = ({ setFosterbarn }: FosterbarnProps) => { + const { readOnly } = useContext(ContainerContext); + const { + control, + register, + formState: { errors }, + } = useForm({ mode: 'onBlur' }); + const { fields, append, remove } = useFieldArray({ + control, + name: 'fosterbarn', + }); + + const fosterbarnFormValues: { fødselsnummer: string }[] = useWatch({ name: 'fosterbarn', control }); + + useEffect(() => { + const unikeFosterbarn = new Set(fosterbarnFormValues?.map(fosterbarn => fosterbarn.fødselsnummer)); + setFosterbarn([...unikeFosterbarn]); + }, [fosterbarnFormValues]); + + return ( + + + + + Fosterbarn + + + {fields.length > 0 && ( + + + + + + Fødselsnummer + Fjern + + + + {fields.map((field, index) => ( + + {`Fosterbarn ${index + 1}`} + + { + if (validator.fnr(value).status === 'valid') { + return ''; + } + return 'Ugyldig fødselsnummer'; + }, + }, + })} + hideLabel + label="Fødselsnummer" + size="small" + htmlSize={11} + error={errors.fosterbarn?.[index]?.fødselsnummer?.message} + /> + + +
    +
    + )} + + +
    +
    + ); +}; + +export default Fosterbarn; diff --git a/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiode-vurderingsdetaljer/OmsorgsperiodeVurderingsdetaljer.tsx b/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiode-vurderingsdetaljer/OmsorgsperiodeVurderingsdetaljer.tsx new file mode 100644 index 0000000000..94512f1ab5 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiode-vurderingsdetaljer/OmsorgsperiodeVurderingsdetaljer.tsx @@ -0,0 +1,104 @@ +import { Box, Margin, DetailView, LabelledContent, LinkButton, AssessedBy } from '@navikt/ft-plattform-komponenter'; +import React, { useContext } from 'react'; +import { useIntl } from 'react-intl'; +import Omsorgsperiode from '../../../types/Omsorgsperiode'; +import Relasjon from '../../../types/Relasjon'; +import Ytelsestype from '../../../types/Ytelsestype'; +import ContainerContext from '../../context/ContainerContext'; +import WriteAccessBoundContent from '../write-access-bound-content/WriteAccessBoundContent'; +import styles from './omsorgsperiodeVurderingsdetaljer.css'; + +interface OmsorgsperiodeVurderingsdetaljerProps { + omsorgsperiode: Omsorgsperiode; + onEditClick: () => void; + registrertForeldrerelasjon: boolean; +} + +const OmsorgsperiodeVurderingsdetaljer = ({ + omsorgsperiode, + onEditClick, + registrertForeldrerelasjon, +}: OmsorgsperiodeVurderingsdetaljerProps): JSX.Element => { + const intl = useIntl(); + const { sakstype, saksbehandlere } = useContext(ContainerContext); + const erOMP = sakstype === Ytelsestype.OMP; + const begrunnelseRenderer = () => { + let label = intl.formatMessage({ id: 'vurdering.hjemmel' }); + let begrunnelse = ''; + if (omsorgsperiode.erManueltVurdert()) { + begrunnelse = omsorgsperiode.begrunnelse; + } else if (omsorgsperiode.erAutomatiskVurdert()) { + if (!erOMP) { + begrunnelse = registrertForeldrerelasjon + ? 'Søker er folkeregistrert forelder' + : 'Søker er ikke folkeregistrert forelder'; + } + label = 'Automatisk vurdert'; + } + return ( + <> + + + + ); + }; + + const resultatRenderer = () => { + if (omsorgsperiode.erOppfylt()) { + return 'Ja'; + } + if (omsorgsperiode.erIkkeOppfylt()) { + return 'Nei'; + } + return null; + }; + + const skalViseRelasjonsbeskrivelse = + omsorgsperiode.relasjon?.toUpperCase() === Relasjon.ANNET.toUpperCase() && omsorgsperiode.relasjonsbeskrivelse; + + const harSøkerOmsorgenLabel = erOMP + ? 'Er vilkåret oppfylt for denne perioden?' + : 'Har søker omsorgen for barnet i denne perioden?'; + + return ( + ( + ( + + Rediger vurdering + + )} + /> + )} + > + {omsorgsperiode.erManueltVurdert() && ( + <> + {omsorgsperiode.relasjon && ( + + + + )} + {skalViseRelasjonsbeskrivelse && ( + + + + )} + + )} + {begrunnelseRenderer()} + + + + + + + + ); +}; + +export default OmsorgsperiodeVurderingsdetaljer; diff --git a/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiode-vurderingsdetaljer/omsorgsperiodeVurderingsdetaljer.css b/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiode-vurderingsdetaljer/omsorgsperiodeVurderingsdetaljer.css new file mode 100644 index 0000000000..9411969476 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiode-vurderingsdetaljer/omsorgsperiodeVurderingsdetaljer.css @@ -0,0 +1,3 @@ +.endreLink { + margin-left: 1rem; +} diff --git a/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiodeoversikt-messages/OmsorgsperiodeoversiktMessages.tsx b/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiodeoversikt-messages/OmsorgsperiodeoversiktMessages.tsx new file mode 100644 index 0000000000..cdf8ca1856 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiodeoversikt-messages/OmsorgsperiodeoversiktMessages.tsx @@ -0,0 +1,29 @@ +import { Box, Margin } from '@navikt/ft-plattform-komponenter'; +import Alertstripe from 'nav-frontend-alertstriper'; +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import Omsorgsperiodeoversikt from '../../../types/Omsorgsperiodeoversikt'; +import { getStringMedPerioder } from '../../../util/periodUtils'; +import styles from './omsorgsperiodeoversiktMessages.css'; + +interface OmsorgsperiodeoversiktMessagesProps { + omsorgsperiodeoversikt: Omsorgsperiodeoversikt; +} + +const OmsorgsperiodeoversiktMessages = ({ + omsorgsperiodeoversikt, +}: OmsorgsperiodeoversiktMessagesProps): JSX.Element => { + if (omsorgsperiodeoversikt.harPerioderTilVurdering()) { + const perioderTilVurdering = omsorgsperiodeoversikt.finnPerioderTilVurdering().map(({ periode }) => periode); + return ( + + + + + + ); + } + return null; +}; + +export default OmsorgsperiodeoversiktMessages; diff --git a/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiodeoversikt-messages/omsorgsperiodeoversiktMessages.css b/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiodeoversikt-messages/omsorgsperiodeoversiktMessages.css new file mode 100644 index 0000000000..b28aea0b2f --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiodeoversikt-messages/omsorgsperiodeoversiktMessages.css @@ -0,0 +1,3 @@ +.alertstripe :global .alertstripe__tekst { + max-width: 50rem; +} diff --git a/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiodeoversikt/Omsorgsperiodeoversikt.tsx b/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiodeoversikt/Omsorgsperiodeoversikt.tsx new file mode 100644 index 0000000000..cbb976fd28 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/omsorgsperiodeoversikt/Omsorgsperiodeoversikt.tsx @@ -0,0 +1,76 @@ +import React, { useContext, useEffect } from 'react'; +import { NavigationWithDetailView } from '@navikt/ft-plattform-komponenter'; +import hash from 'object-hash'; +import OmsorgsperiodeoversiktType from '../../../types/Omsorgsperiodeoversikt'; +import Omsorgsperiode from '../../../types/Omsorgsperiode'; +import OmsorgsperiodeoversiktMessages from '../omsorgsperiodeoversikt-messages/OmsorgsperiodeoversiktMessages'; +import Periodenavigasjon from '../periodenavigasjon/Periodenavigasjon'; +import OmsorgsperiodeVurderingsdetaljer from '../omsorgsperiode-vurderingsdetaljer/OmsorgsperiodeVurderingsdetaljer'; +import VurderingAvOmsorgsperioderForm from '../vurdering-av-omsorgsperioder-form/VurderingAvOmsorgsperioderForm'; +import Fosterbarn from '../fosterbarn/Fosterbarn'; +import ContainerContext from '../../context/ContainerContext'; +import Ytelsestype from '../../../types/Ytelsestype'; + +interface OmsorgsperiodeoversiktProps { + omsorgsperiodeoversikt: OmsorgsperiodeoversiktType; +} + +const Omsorgsperiodeoversikt = ({ omsorgsperiodeoversikt }: OmsorgsperiodeoversiktProps): JSX.Element => { + const { readOnly, sakstype } = useContext(ContainerContext); + const [valgtPeriode, setValgtPeriode] = React.useState(null); + const [erRedigeringsmodus, setErRedigeringsmodus] = React.useState(false); + const [fosterbarn, setFosterbarn] = React.useState([]); + + const perioderTilVurdering = omsorgsperiodeoversikt.finnPerioderTilVurdering(); + const vurderteOmsorgsperioder = omsorgsperiodeoversikt.finnVurdertePerioder(); + + const velgPeriode = (periode: Omsorgsperiode) => { + setValgtPeriode(periode); + setErRedigeringsmodus(false); + }; + + useEffect(() => { + if (omsorgsperiodeoversikt.harPerioderTilVurdering()) { + setValgtPeriode(perioderTilVurdering[0]); + } + }, []); + + return ( + <> + + {sakstype === Ytelsestype.OMP && !readOnly && } + ( + + )} + showDetailSection={!!valgtPeriode} + detailSection={() => { + if (perioderTilVurdering.includes(valgtPeriode) || erRedigeringsmodus) { + return ( + velgPeriode(null)} + fosterbarn={fosterbarn} + /> + ); + } + return ( + setErRedigeringsmodus(true)} + registrertForeldrerelasjon={omsorgsperiodeoversikt.registrertForeldrerelasjon} + /> + ); + }} + /> + + ); +}; + +export default Omsorgsperiodeoversikt; diff --git a/packages/fakta-omsorgen-for/src/ui/components/periode-som-skal-vurderes/PeriodeSomSkalVurderes.tsx b/packages/fakta-omsorgen-for/src/ui/components/periode-som-skal-vurderes/PeriodeSomSkalVurderes.tsx new file mode 100644 index 0000000000..e7aa0b36fc --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/periode-som-skal-vurderes/PeriodeSomSkalVurderes.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Period } from '@fpsak-frontend/utils'; +import { ContentWithTooltip, WarningIcon } from '@navikt/ft-plattform-komponenter'; +import styles from './periodeSomSkalVurderes.css'; + +interface PeriodeSomSkalVurderesProps { + periode: Period; +} + +const PeriodeSomSkalVurderes = ({ periode }: PeriodeSomSkalVurderesProps): JSX.Element => { + const period = new Period(periode.fom, periode.tom); + return ( +
    + Type + + + +
    +
    +

    + {period.prettifyPeriod()} +

    +
    +
    +
    + ); +}; + +export default PeriodeSomSkalVurderes; diff --git a/packages/fakta-omsorgen-for/src/ui/components/periode-som-skal-vurderes/periodeSomSkalVurderes.css b/packages/fakta-omsorgen-for/src/ui/components/periode-som-skal-vurderes/periodeSomSkalVurderes.css new file mode 100644 index 0000000000..7ded51f17e --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/periode-som-skal-vurderes/periodeSomSkalVurderes.css @@ -0,0 +1,32 @@ +.periodeSomSkalVurderes { + display: flex; + align-items: center; + cursor: pointer; +} + +.periodeSomSkalVurderes__texts { + margin-left: 1.625rem; + display: flex; + align-items: center; +} + +.periodeSomSkalVurderes__texts__period { + margin: 0; + color: #0067c5; + text-decoration: underline; +} + +.periodeSomSkalVurderes__etikett { + margin-left: 1.375rem; +} + +.periodeSomSkalVurderes .visuallyHidden { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} diff --git a/packages/fakta-omsorgen-for/src/ui/components/periodenavigasjon/Periodenavigasjon.tsx b/packages/fakta-omsorgen-for/src/ui/components/periodenavigasjon/Periodenavigasjon.tsx new file mode 100644 index 0000000000..49e90c220e --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/periodenavigasjon/Periodenavigasjon.tsx @@ -0,0 +1,80 @@ +import { Period } from '@fpsak-frontend/utils'; +import { Box, Margin, InteractiveList } from '@navikt/ft-plattform-komponenter'; +import { Undertittel } from 'nav-frontend-typografi'; +import React, { useEffect } from 'react'; +import Omsorgsperiode from '../../../types/Omsorgsperiode'; +import { usePrevious } from '../../../util/hooks'; +import { sortPeriodsByFomDate } from '../../../util/periodUtils'; +import PeriodeSomSkalVurderes from '../periode-som-skal-vurderes/PeriodeSomSkalVurderes'; +import VurderingsperiodeElement from '../vurderingsperiode/VurderingsperiodeElement'; +import styles from './periodenavigasjon.css'; + +interface PeriodenavigasjonProps { + perioderTilVurdering: Omsorgsperiode[]; + vurdertePerioder: Omsorgsperiode[]; + onPeriodeValgt: (periode: Omsorgsperiode) => void; + harValgtPeriode?: boolean; +} + +const Periodenavigasjon = ({ + perioderTilVurdering, + vurdertePerioder, + onPeriodeValgt, + harValgtPeriode, +}: PeriodenavigasjonProps): JSX.Element => { + const harPerioderSomSkalVurderes = perioderTilVurdering?.length > 0; + const [activeIndex, setActiveIndex] = React.useState(harPerioderSomSkalVurderes ? 0 : -1); + const previousHarValgtPeriode = usePrevious(harValgtPeriode); + + useEffect(() => { + if (harValgtPeriode === false && previousHarValgtPeriode === true) { + setActiveIndex(-1); + } + }, [harValgtPeriode]); + + const vurdertePerioderElements = vurdertePerioder + .sort((op1, op2) => { + const omsorgsperiode1 = new Period(op1.periode.fom, op1.periode.tom); + const omsorgsperiode2 = new Period(op2.periode.fom, op2.periode.tom); + return sortPeriodsByFomDate(omsorgsperiode1, omsorgsperiode2); + }) + .map(omsorgsperiode => { + const { periode } = omsorgsperiode; + return ; + }); + + const periodeTilVurderingElements = perioderTilVurdering.map(({ periode }) => ( + + )); + + const perioder = [...perioderTilVurdering, ...vurdertePerioder]; + const elements = [...periodeTilVurderingElements, ...vurdertePerioderElements]; + const antallPerioder = elements.length; + + return ( +
    + + Alle perioder + + {antallPerioder === 0 &&

    Ingen vurderinger å vise

    } + {antallPerioder > 0 && ( +
    + ({ + content: element, + active: activeIndex === currentIndex, + key: `${currentIndex}`, + onClick: () => { + setActiveIndex(currentIndex); + const periodeIndex = elements.indexOf(element); + onPeriodeValgt(perioder[periodeIndex]); + }, + }))} + /> +
    + )} +
    + ); +}; + +export default Periodenavigasjon; diff --git a/packages/fakta-omsorgen-for/src/ui/components/periodenavigasjon/periodenavigasjon.css b/packages/fakta-omsorgen-for/src/ui/components/periodenavigasjon/periodenavigasjon.css new file mode 100644 index 0000000000..37ada1a77d --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/periodenavigasjon/periodenavigasjon.css @@ -0,0 +1,32 @@ +.nyVurderingKnapp { + flex-basis: 40%; + margin-left: auto; +} + +.vurderingsvelgerContainer { + flex-grow: 1; + margin-top: 1rem; +} + +.vurderingsperiode__postElementContainer { + margin-left: 1.375rem; + display: flex; +} + +.vurderingsperiode__postElementContainer :nth-child(2) { + margin-left: 1rem; +} + +.vurderingsnavigasjon { + position: relative; +} + +.vurderingsnavigasjon__opprettVurderingKnapp { + position: absolute; + right: 0; + top: 4px; +} + +.vurderingsnavigasjon__heading { + padding: 1.25rem 0.9375rem 0 1.375rem; +} diff --git a/packages/fakta-omsorgen-for/src/ui/components/vurdering-av-omsorgsperioder-form/VurderingAvOmsorgsperioderForm.tsx b/packages/fakta-omsorgen-for/src/ui/components/vurdering-av-omsorgsperioder-form/VurderingAvOmsorgsperioderForm.tsx new file mode 100644 index 0000000000..12417096b2 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/vurdering-av-omsorgsperioder-form/VurderingAvOmsorgsperioderForm.tsx @@ -0,0 +1,236 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; +import { FormProvider, useForm, useWatch } from 'react-hook-form'; + +import { getPeriodDifference, Period } from '@fpsak-frontend/utils'; +import { Box, Margin, DetailView, Form, LabelledContent } from '@navikt/ft-plattform-komponenter'; +import { PeriodpickerListRHF, RadioGroupPanelRHF, TextAreaRHF } from '@fpsak-frontend/form'; +import { Label } from '@navikt/ds-react'; +import { AlertStripeInfo } from 'nav-frontend-alertstriper'; + +import Omsorgsperiode from '../../../types/Omsorgsperiode'; +import Relasjon from '../../../types/Relasjon'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import ContainerContext from '../../context/ContainerContext'; +import { required } from '../../form/validators/index'; +import AddButton from '../add-button/AddButton'; +import DeleteButton from '../delete-button/DeleteButton'; +import styles from './vurderingAvOmsorgsperioderForm.css'; +import Ytelsestype from '../../../types/Ytelsestype'; + +export enum FieldName { + BEGRUNNELSE = 'begrunnelse', + HAR_SØKER_OMSORGEN_FOR_I_PERIODE = 'harSøkerOmsorgenForIPeriode', + PERIODER = 'perioder', +} + +enum RadioOptions { + HELE = 'hele', + DELER = 'deler', + NEI = 'nei', +} + +interface FormPeriod { + period: Period; +} + +const finnResterendePerioder = (perioderFraForm: FormPeriod[], periodeTilVurdering: Period) => { + const formatertePerioderFraForm = perioderFraForm.map(periode => new Period(periode.period.fom, periode.period.tom)); + const resterendePerioder = + formatertePerioderFraForm.length > 0 && getPeriodDifference([periodeTilVurdering], formatertePerioderFraForm); + + return resterendePerioder; +}; + +interface VurderingAvOmsorgsperioderFormProps { + omsorgsperiode: Omsorgsperiode; + onAvbryt: () => void; + fosterbarn: string[]; +} + +interface VurderingAvOmsorgsperioderFormState { + [FieldName.BEGRUNNELSE]: string; + [FieldName.PERIODER]: FormPeriod[]; + [FieldName.HAR_SØKER_OMSORGEN_FOR_I_PERIODE]: RadioOptions; +} + +const VurderingAvOmsorgsperioderForm = ({ + omsorgsperiode, + onAvbryt, + fosterbarn, +}: VurderingAvOmsorgsperioderFormProps): JSX.Element => { + const { onFinished, readOnly, sakstype } = React.useContext(ContainerContext); + const erOMP = sakstype === Ytelsestype.OMP; + const intl = useIntl(); + const formMethods = useForm({ + defaultValues: { + [FieldName.PERIODER]: [{ period: omsorgsperiode.periode }], + [FieldName.BEGRUNNELSE]: omsorgsperiode.begrunnelse || '', + [FieldName.HAR_SØKER_OMSORGEN_FOR_I_PERIODE]: undefined, + }, + }); + + const handleSubmit = (formState: VurderingAvOmsorgsperioderFormState) => { + const { begrunnelse, perioder, harSøkerOmsorgenForIPeriode } = formState; + + let vurdertePerioder; + const fosterbarnForOmsorgspenger = erOMP ? fosterbarn : undefined; + if (harSøkerOmsorgenForIPeriode === RadioOptions.DELER) { + vurdertePerioder = perioder.map(({ period }) => ({ + periode: period, + resultat: Vurderingsresultat.OPPFYLT, + begrunnelse, + })); + + const resterendePerioder = finnResterendePerioder(perioder, omsorgsperiode.periode); + const perioderUtenOmsorg = resterendePerioder.map(periode => ({ + periode, + resultat: Vurderingsresultat.IKKE_OPPFYLT, + begrunnelse, + })); + vurdertePerioder = vurdertePerioder.concat(perioderUtenOmsorg); + } else { + vurdertePerioder = [ + { + periode: omsorgsperiode.periode, + resultat: + harSøkerOmsorgenForIPeriode === RadioOptions.HELE + ? Vurderingsresultat.OPPFYLT + : Vurderingsresultat.IKKE_OPPFYLT, + begrunnelse, + }, + ]; + } + onFinished(vurdertePerioder, fosterbarnForOmsorgspenger); + }; + + const perioder = useWatch({ control: formMethods.control, name: FieldName.PERIODER }); + const harSøkerOmsorgenFor = useWatch({ + control: formMethods.control, + name: FieldName.HAR_SØKER_OMSORGEN_FOR_I_PERIODE, + }); + const resterendePerioder = finnResterendePerioder(perioder, omsorgsperiode.periode); + const skalViseRelasjonsbeskrivelse = + omsorgsperiode.relasjon?.toUpperCase() === Relasjon.ANNET.toUpperCase() && omsorgsperiode.relasjonsbeskrivelse; + + return ( +
    + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + + {omsorgsperiode.relasjon && ( + + + + )} + {skalViseRelasjonsbeskrivelse && ( + + + + )} +
    + + + {erOMP &&

    {intl.formatMessage({ id: 'vurdering.hjemmel.hjelpetekst' })}

    } + +
    + + + + {harSøkerOmsorgenFor === RadioOptions.DELER && ( + + + numberOfItems > 1 && ( + { + fieldArrayMethods.remove(index); + }} + /> + ) + } + renderAfterFieldArray={fieldArrayMethods => ( + + fieldArrayMethods.append({ fom: '', tom: '' })} + id="leggTilPeriodeKnapp" + /> + + )} + validators={{ + overlaps: (valgtPeriode: Period) => { + const andreValgtePerioder = formMethods + .getValues() + .perioder.filter( + (periodWrapper: { period: Partial }) => periodWrapper.period !== valgtPeriode, + ) + .map(({ period }: { period: Partial }) => new Period(period.fom, period.tom)); + const { fom, tom } = valgtPeriode; + const valgtPeriodePeriod = new Period(fom, tom); + if (valgtPeriodePeriod.overlapsWithSomePeriodInList(andreValgtePerioder)) { + return 'Omsorgsperiodene kan ikke overlappe'; + } + return null; + }, + }} + /> + + )} + {resterendePerioder.length > 0 && ( + + + ( +

    + {periode.prettifyPeriod()} +

    + ))} + /> +
    +
    + )} +
    +
    +
    +
    + ); +}; + +export default VurderingAvOmsorgsperioderForm; diff --git a/packages/fakta-omsorgen-for/src/ui/components/vurdering-av-omsorgsperioder-form/vurderingAvOmsorgsperioderForm.css b/packages/fakta-omsorgen-for/src/ui/components/vurdering-av-omsorgsperioder-form/vurderingAvOmsorgsperioderForm.css new file mode 100644 index 0000000000..c949550167 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/vurdering-av-omsorgsperioder-form/vurderingAvOmsorgsperioderForm.css @@ -0,0 +1,3 @@ +.vurderingAvOmsorgsperioderForm { + margin-bottom: 8rem; +} diff --git a/packages/fakta-omsorgen-for/src/ui/components/vurderingsperiode/VurderingsperiodeElement.tsx b/packages/fakta-omsorgen-for/src/ui/components/vurderingsperiode/VurderingsperiodeElement.tsx new file mode 100644 index 0000000000..65721961e7 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/vurderingsperiode/VurderingsperiodeElement.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Period } from '@fpsak-frontend/utils'; +import { ContentWithTooltip, GreenCheckIconFilled, RedCrossIconFilled } from '@navikt/ft-plattform-komponenter'; +import Vurderingsresultat from '../../../types/Vurderingsresultat'; +import styles from './vurderingsperiodeElement.css'; + +interface VurderingsperiodeElementProps { + periode: Period; + resultat: Vurderingsresultat; + renderAfterElement?: () => React.ReactNode; +} + +const renderIcon = (resultat: Vurderingsresultat) => { + if (resultat === Vurderingsresultat.OPPFYLT) { + return ( + + + + ); + } + if (resultat === Vurderingsresultat.IKKE_OPPFYLT) { + return ( + + + + ); + } + return null; +}; + +const VurderingsperiodeElement = ({ + periode, + resultat, + renderAfterElement, +}: VurderingsperiodeElementProps): JSX.Element => { + const period = new Period(periode.fom, periode.tom); + return ( +
    + Type + {renderIcon(resultat)} +
    +

    + Periode + {period.prettifyPeriod()} +

    +
    + {renderAfterElement && renderAfterElement()} +
    + ); +}; + +export default VurderingsperiodeElement; diff --git a/packages/fakta-omsorgen-for/src/ui/components/vurderingsperiode/vurderingsperiodeElement.css b/packages/fakta-omsorgen-for/src/ui/components/vurderingsperiode/vurderingsperiodeElement.css new file mode 100644 index 0000000000..7999884b32 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/vurderingsperiode/vurderingsperiodeElement.css @@ -0,0 +1,40 @@ +.vurderingsperiodeElement { + display: flex; + cursor: pointer; +} + +.vurderingsperiodeElement__texts { + display: flex; + align-items: center; + margin-left: 1.625rem; +} + +.vurderingsperiodeElement__texts__period { + display: inline-block; + margin: 0; + color: #0067c5; + text-decoration: underline; +} + +.vurderingsperiodeElement__texts__resultat { + display: inline-block; + margin: 0 0 0 1.375rem; +} + +.vurderingsperiodeElement__texts__parterIcon { + display: inline-block; + vertical-align: top; + margin-top: -0.125rem; + margin-left: 1.625rem; +} + +.vurderingsperiodeElement .visuallyHidden { + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +} diff --git a/packages/fakta-omsorgen-for/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx b/packages/fakta-omsorgen-for/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx new file mode 100644 index 0000000000..05fe3c3eaf --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/components/write-access-bound-content/WriteAccessBoundContent.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import ContainerContext from '../../context/ContainerContext'; + +interface WriteAccessBoundContentProps { + contentRenderer: () => JSX.Element; + otherRequirementsAreMet?: boolean; +} + +const WriteAccessBoundContent = ({ + contentRenderer, + otherRequirementsAreMet, +}: WriteAccessBoundContentProps): JSX.Element => { + const { readOnly } = React.useContext(ContainerContext); + if (readOnly === false && (otherRequirementsAreMet === true || otherRequirementsAreMet === undefined)) { + return contentRenderer(); + } + return null; +}; + +export default WriteAccessBoundContent; diff --git a/packages/fakta-omsorgen-for/src/ui/context/ContainerContext.ts b/packages/fakta-omsorgen-for/src/ui/context/ContainerContext.ts new file mode 100644 index 0000000000..b7eae9fc94 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/context/ContainerContext.ts @@ -0,0 +1,5 @@ +import React from 'react'; +import { ContainerContract } from '../../types/ContainerContract'; + +const ContainerContext = React.createContext(null); +export default ContainerContext; diff --git a/packages/fakta-omsorgen-for/src/ui/form/validators/index.ts b/packages/fakta-omsorgen-for/src/ui/form/validators/index.ts new file mode 100644 index 0000000000..7a8ef8e83d --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/form/validators/index.ts @@ -0,0 +1,73 @@ +import { Dayjs } from 'dayjs'; +import { Period } from '@fpsak-frontend/utils'; +import { dateFromString } from '../../../util/dateUtils'; +import { tomorrow } from '../../../constants/dateConstants'; + +export function required(v: string | number): string | boolean { + if (v === null || v === undefined || v === '') { + return 'Du må oppgi en verdi'; + } + return true; +} + +export function dateIsNotInTheFuture(dateString: string): string | boolean { + const date: Dayjs = dateFromString(dateString); + if (date.isSame(tomorrow) || date.isAfter(tomorrow)) { + return 'Datoen kan ikke settes senere enn dagens dato'; + } + return true; +} + +export const detErTilsynsbehovPåDatoen = (dato: string, perioderMedTilsynsbehov: Period[]): string | boolean => { + const detErTilsynsbehovPåDato = perioderMedTilsynsbehov.some(periode => + new Period(periode.fom, periode.tom).includesDate(dato), + ); + if (detErTilsynsbehovPåDato) { + return true; + } + return 'Dato må være innenfor en periode med tilsynsbehov'; +}; + +export const datoenInngårISøknadsperioden = (dato: string, søknadsperiode: Period): string | boolean => { + if (søknadsperiode.includesDate(dato)) { + return true; + } + + return 'Dato må være innenfor søknadsperioden'; +}; + +export const detErIngenInnleggelsePåDato = (dato: string, innleggelsesperioder: Period[]): string | boolean => { + const detErInnleggelsePåDato = innleggelsesperioder.some(periode => + new Period(periode.fom, periode.tom).includesDate(dato), + ); + if (detErInnleggelsePåDato) { + return 'Dato må være utenfor innleggelsesperioden(e)'; + } + return true; +}; + +export const datoErInnenforResterendeVurderingsperioder = ( + dato: string, + resterendeVurderingsperioder: Period[], +): string | true => { + const datoErInnenfor = resterendeVurderingsperioder.some(period => + new Period(period.fom, period.tom).includesDate(dato), + ); + + if (datoErInnenfor) { + return true; + } + + return 'Dato må være innenfor periodene som vurderes'; +}; + +export const fomDatoErFørTomDato = (periode: Period): string | true => { + const fom = dateFromString(periode.fom); + const tom = dateFromString(periode.tom); + + if (fom.isAfter(tom)) { + return 'Fra-dato må være før til-dato'; + } + + return true; +}; diff --git a/packages/fakta-omsorgen-for/src/ui/mainComponent.css b/packages/fakta-omsorgen-for/src/ui/mainComponent.css new file mode 100644 index 0000000000..1235348a0e --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/mainComponent.css @@ -0,0 +1,4 @@ +.mainComponent { + max-width: 75rem; + color: #262626; +} diff --git a/packages/fakta-omsorgen-for/src/ui/reducer.ts b/packages/fakta-omsorgen-for/src/ui/reducer.ts new file mode 100644 index 0000000000..5191ce926d --- /dev/null +++ b/packages/fakta-omsorgen-for/src/ui/reducer.ts @@ -0,0 +1,40 @@ +import Omsorgsperiodeoversikt from '../types/Omsorgsperiodeoversikt'; +import ActionType from './actionTypes'; + +interface MainComponentState { + omsorgsperiodeoversikt: Omsorgsperiodeoversikt; + omsorgsperiodeoversiktHarFeilet: boolean; + isLoading: boolean; +} + +interface Action { + type: ActionType; + omsorgsperiodeoversikt?: Omsorgsperiodeoversikt; +} + +const mainComponentReducer = (state: MainComponentState, action: Action): Partial => { + switch (action.type) { + case ActionType.OK: + return { + omsorgsperiodeoversikt: action.omsorgsperiodeoversikt, + omsorgsperiodeoversiktHarFeilet: false, + isLoading: false, + }; + case ActionType.FAILED: + return { + omsorgsperiodeoversikt: null, + omsorgsperiodeoversiktHarFeilet: true, + isLoading: false, + }; + case ActionType.PENDING: + return { + omsorgsperiodeoversikt: null, + omsorgsperiodeoversiktHarFeilet: false, + isLoading: true, + }; + default: + return { ...state }; + } +}; + +export default mainComponentReducer; diff --git a/packages/fakta-omsorgen-for/src/util/dateUtils.ts b/packages/fakta-omsorgen-for/src/util/dateUtils.ts new file mode 100644 index 0000000000..c3a1584296 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/util/dateUtils.ts @@ -0,0 +1,13 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; + +const dateFormats = ['YYYY-MM-DD', 'DD.MM.YYYY']; + +dayjs.extend(utc); +dayjs.extend(customParseFormat); + +// eslint-disable-next-line import/prefer-default-export +export function dateFromString(dateString: string): dayjs.Dayjs { + return dayjs(dateString, dateFormats).utc(true); +} diff --git a/packages/fakta-omsorgen-for/src/util/hooks.ts b/packages/fakta-omsorgen-for/src/util/hooks.ts new file mode 100644 index 0000000000..3d5ef27692 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/util/hooks.ts @@ -0,0 +1,10 @@ +import { useEffect, useRef } from 'react'; + +// eslint-disable-next-line import/prefer-default-export +export const usePrevious = (value: boolean): boolean => { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }); + return ref.current; +}; diff --git a/packages/fakta-omsorgen-for/src/util/periodUtils.ts b/packages/fakta-omsorgen-for/src/util/periodUtils.ts new file mode 100644 index 0000000000..d5cae6cf05 --- /dev/null +++ b/packages/fakta-omsorgen-for/src/util/periodUtils.ts @@ -0,0 +1,31 @@ +import { Period } from '@fpsak-frontend/utils'; + +export const getStringMedPerioder = (perioder: Period[]): string => { + if (perioder.length === 1) { + return `perioden ${perioder[0].prettifyPeriod()}`; + } + + let perioderString = ''; + perioder.forEach((periode, index) => { + const prettyPeriod = periode.prettifyPeriod(); + if (index === 0) { + perioderString = prettyPeriod; + } else if (index === perioder.length - 1) { + perioderString = `${perioderString} og ${prettyPeriod}`; + } else { + perioderString = `${perioderString}, ${prettyPeriod}`; + } + }); + + return `periodene ${perioderString}`; +}; + +export const sortPeriodsByFomDate = (period1: Period, period2: Period): number => { + if (period1.startsBefore(period2)) { + return 1; + } + if (period2.startsBefore(period1)) { + return -1; + } + return 0; +}; diff --git a/packages/fakta-omsorgen-for/src/util/renderers.tsx b/packages/fakta-omsorgen-for/src/util/renderers.tsx new file mode 100644 index 0000000000..d4412246cf --- /dev/null +++ b/packages/fakta-omsorgen-for/src/util/renderers.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import MainComponent from '../ui/MainComponent'; +import { ContainerContract } from '../types/ContainerContract'; + +function prepare() { + if (process.env.NODE_ENV !== 'production') { + return import('../../mock/browser').then(({ worker }) => worker.start({ onUnhandledRequest: 'bypass' })); + } + + return Promise.resolve(); +} + +const renderAppInSuccessfulState = (appId: string, data: ContainerContract): void => { + prepare().then(() => { + const container = document.getElementById(appId); + const root = createRoot(container); + root.render(); + }); +}; + +export default { + renderAppInSuccessfulState, +}; diff --git a/packages/fakta-omsorgen-for/src/util/utils.ts b/packages/fakta-omsorgen-for/src/util/utils.ts new file mode 100644 index 0000000000..311316eead --- /dev/null +++ b/packages/fakta-omsorgen-for/src/util/utils.ts @@ -0,0 +1,14 @@ +import * as messages from '../nb_NO'; +import Ytelsestype from '../types/Ytelsestype'; + +// eslint-disable-next-line import/prefer-default-export +export const teksterForSakstype = (sakstype: string) => { + if (sakstype === Ytelsestype.PSB) { + return messages.pleiepenger; + } + + if (sakstype === Ytelsestype.OMP) { + return messages.omsorgspenger; + } + return messages.pleiepenger; +}; diff --git a/packages/fakta-omsorgen-for/webpack/webpack-config.development.js b/packages/fakta-omsorgen-for/webpack/webpack-config.development.js new file mode 100644 index 0000000000..151f65b99d --- /dev/null +++ b/packages/fakta-omsorgen-for/webpack/webpack-config.development.js @@ -0,0 +1,40 @@ +/* eslint-disable import/extensions */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const webpack = require('webpack'); +const path = require('path'); +const { merge } = require('webpack-merge'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const WebpackDevServer = require('webpack-dev-server'); +const commonWebpackConfig = require('./webpack.common.js'); + +const webpackConfig = merge(commonWebpackConfig, { + entry: `${path.resolve(__dirname, '../', 'src')}/dev/app.ts`, + mode: 'development', + devtool: 'eval-cheap-module-source-map', + plugins: [ + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, '../index.html'), + }), + ], +}); + +const port = 8484; +const devServerOptions = { + hot: true, + headers: { + 'Access-Control-Allow-Origin': 'http://localhost:9000', + }, + port, +}; + +const compiler = webpack(webpackConfig); +const devServer = new WebpackDevServer(devServerOptions, compiler); +compiler.close(() => console.info('Compiler closed')); + +devServer.startCallback(error => { + if (error) { + console.error(error); + } else { + console.log(`Listening at port ${port}`); + } +}); diff --git a/packages/fakta-omsorgen-for/webpack/webpack.common.js b/packages/fakta-omsorgen-for/webpack/webpack.common.js new file mode 100644 index 0000000000..ffc2e21ed2 --- /dev/null +++ b/packages/fakta-omsorgen-for/webpack/webpack.common.js @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const process = require('process'); +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const cssExtractLoaderConfig = { + loader: MiniCssExtractPlugin.loader, +}; + +const nodeModules = path.resolve(__dirname, '../node_modules'); +const rootNodeModules = path.resolve(__dirname, '../../../node_modules'); + +module.exports = { + resolve: { + extensions: ['.ts', '.tsx', '.js', '.css'], + }, + module: { + rules: [ + { + test: /\.(ts|js)x?$/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + rootMode: 'upward', + }, + }, + }, + { + test: /\.css$/, + use: [ + cssExtractLoaderConfig, + { + loader: 'css-loader', + options: { + importLoaders: 1, + modules: { + localIdentName: '[name]_[local]_[contenthash:base64:5]', + }, + }, + }, + ], + exclude: [nodeModules, rootNodeModules], + }, + { + test: /\.(less|css)?$/, + use: [ + cssExtractLoaderConfig, + { + loader: 'css-loader', + }, + { + loader: 'less-loader', + options: { + lessOptions: { + modifyVars: { + nodeModulesPath: '~', + coreModulePath: '~', + }, + }, + }, + }, + ], + include: [nodeModules, rootNodeModules], + }, + { + test: /\.(jpg|png|svg)$/, + loader: 'file-loader', + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: 'styles.css', + }), + ], +}; diff --git a/packages/fakta-omsorgsdager/index.html b/packages/fakta-omsorgsdager/index.html new file mode 100644 index 0000000000..58171f4318 --- /dev/null +++ b/packages/fakta-omsorgsdager/index.html @@ -0,0 +1,185 @@ + + + Sykdom + + + +
    + + + diff --git a/packages/fakta-omsorgsdager/index.ts b/packages/fakta-omsorgsdager/index.ts new file mode 100644 index 0000000000..25eb168939 --- /dev/null +++ b/packages/fakta-omsorgsdager/index.ts @@ -0,0 +1,3 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as Omsorgsdager } from './src/ui/MainComponent'; +export { default as KomponenterEnum } from './src/types/Komponenter'; diff --git a/packages/fakta-omsorgsdager/package.json b/packages/fakta-omsorgsdager/package.json new file mode 100644 index 0000000000..ccbd9108b3 --- /dev/null +++ b/packages/fakta-omsorgsdager/package.json @@ -0,0 +1,45 @@ +{ + "name": "@k9-sak-web/fakta-omsorgsdager", + "version": "1.0.0", + "module": "index.ts", + "keywords": [], + "author": "NAV IT", + "license": "MIT", + "private": true, + "scripts": { + "dev": "node webpack/webpack-config.development.js" + }, + "devDependencies": { + "jest-axe": "^8.0.0" + }, + "dependencies": { + "@navikt/ds-css": "5.11.2", + "@navikt/ds-react": "5.11.2", + "@navikt/fnrvalidator": "1.3.3", + "classnames": "2.3.2", + "dayjs": "1.11.10", + "lodash": "4.17.21", + "lodash.throttle": "4.1.1", + "nav-datovelger": "12.6.0", + "nav-frontend-alertstriper": "4.0.2", + "nav-frontend-alertstriper-style": "3.0.2", + "nav-frontend-core": "6.0.1", + "nav-frontend-ikoner-assets": "3.0.1", + "nav-frontend-js-utils": "1.0.20", + "nav-frontend-knapper": "3.1.3", + "nav-frontend-knapper-style": "2.1.2", + "nav-frontend-lenker": "2.0.2", + "nav-frontend-lenker-style": "2.0.2", + "nav-frontend-paneler-style": "2.0.2", + "nav-frontend-skjema": "4.0.6", + "nav-frontend-skjema-style": "3.0.3", + "nav-frontend-spinner": "3.0.1", + "nav-frontend-spinner-style": "1.0.2", + "nav-frontend-typografi": "4.0.2", + "nav-frontend-typografi-style": "2.0.2", + "react": "18.2.0", + "react-day-picker": "7.4.10", + "react-dom": "18.2.0", + "react-hook-form": "7.48.2" + } +} diff --git a/packages/fakta-omsorgsdager/src/api/OmsorgApi.ts b/packages/fakta-omsorgsdager/src/api/OmsorgApi.ts new file mode 100644 index 0000000000..77259e5403 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/api/OmsorgApi.ts @@ -0,0 +1,31 @@ +import Omsorgsinfo from '../types/Omsorgsinfo'; +import { get } from '../util/apiUtils'; + +export default class OmsorgApi { + stiTilEndepunkt: string; + + behandlingsid: string; + + constructor(stiTilEndepunkt: string, behandlingsid: string) { + this.stiTilEndepunkt = stiTilEndepunkt; + this.behandlingsid = behandlingsid; + } + + async getVedtak(): Promise { + return get(this.stiTilEndepunkt, { behandlingId: this.behandlingsid }); + } + + async hentInfoOmOmsorg(): Promise { + return this.getVedtak().then(response => { + if (response.vedtak.length && response.vedtak[0]) { + const vedtak = response.vedtak[0]; + const omsorgen = vedtak.løsteBehov.VURDERE_OMSORGEN_FOR || vedtak.uløsteBehov.VURDERE_OMSORGEN_FOR; + const losning = omsorgen?.losning; + if (losning) { + return { harOmsorgen: losning?.harOmsorgen }; + } + } + return { harOmsorgen: false }; + }); + } +} diff --git a/packages/fakta-omsorgsdager/src/dev/app.ts b/packages/fakta-omsorgsdager/src/dev/app.ts new file mode 100644 index 0000000000..2dfec092dc --- /dev/null +++ b/packages/fakta-omsorgsdager/src/dev/app.ts @@ -0,0 +1,9 @@ +import renderers from '../util/renderers'; +import ContainerContract from '../types/ContainerContract'; +import '@navikt/ds-css'; + +// test +(window as any).renderMicrofrontendOmsorgsdagerApp = async (appId, data: ContainerContract) => { + const { renderAppInSuccessfulState } = renderers; + renderAppInSuccessfulState(appId, data); +}; diff --git a/packages/fakta-omsorgsdager/src/types/AleneOmOmsorgenProps.ts b/packages/fakta-omsorgsdager/src/types/AleneOmOmsorgenProps.ts new file mode 100644 index 0000000000..b040bf8502 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/AleneOmOmsorgenProps.ts @@ -0,0 +1,22 @@ +import { FormState } from './FormState'; +import { InformasjonOmVurdertVilkar } from './InformasjonOmVurdertVilkar'; + +export interface AleneOmOmsorgenProps { + behandlingsID: string; + lesemodus?: boolean; + aksjonspunktLost: boolean; + fraDatoFraSoknad: string; + informasjonTilLesemodus?: AleneOmOmsorgenAksjonspunktObjekt; + vedtakFattetVilkarOppfylt: boolean; + erBehandlingstypeRevurdering: boolean; + informasjonOmVilkar?: InformasjonOmVurdertVilkar; + losAksjonspunkt?: (AleneOmOmsorgenAksjonspunktObjekt) => void; + formState: FormState; +} + +export interface AleneOmOmsorgenAksjonspunktObjekt { + begrunnelse: string; + vilkarOppfylt: boolean; + fraDato: string; + tilDato: string; +} diff --git a/packages/fakta-omsorgsdager/src/types/Behov.ts b/packages/fakta-omsorgsdager/src/types/Behov.ts new file mode 100644 index 0000000000..89b2b25e61 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/Behov.ts @@ -0,0 +1,45 @@ +export interface LosteBehovsliste { + VURDERE_KRONISK_SYKT_BARN?: LostBehov; + VURDERE_OMSORGEN_FOR?: LostBehov; + VURDER_MIDLERTIDIG_ALENE?: LostBehov; +} + +export interface UlosteBehovsliste { + VURDERE_KRONISK_SYKT_BARN?: UlostBehov; + VURDERE_OMSORGEN_FOR?: UlostBehov; + VURDER_MIDLERTIDIG_ALENE?: UlostBehov; +} + +export interface BehovslisteInnsending { + VURDERE_KRONISK_SYKT_BARN?: Legeerklaering; + VURDERE_OMSORGEN_FOR?: OmsorgenFor; + VURDER_MIDLERTIDIG_ALENE?: MidlertidigAlene; +} + +interface LostBehov { + grunnlag: any; + lovhenvisninger: { + innvilget: { [henvisning: string]: string }; + avslått: { [henvisning: string]: string }; + }; + løsning: T; +} + +type UlostBehov = Record; + +interface MidlertidigAlene { + vurdering: string; + erSøkerenMidlertidigAleneOmOmsorgen: boolean; + gyldigFraOgMed: string; + gyldigTilOgMed: string; +} + +interface Legeerklaering { + vurdering: string; + barnetErKroniskSyktEllerHarEnFunksjonshemning: boolean; + erSammenhengMedSøkersRisikoForFraværFraArbeid: boolean; +} + +export interface OmsorgenFor { + harOmsorgen?: boolean; // TODO: Avklare formatet på dette aksjonspunktet +} diff --git a/packages/fakta-omsorgsdager/src/types/ContainerContract.ts b/packages/fakta-omsorgsdager/src/types/ContainerContract.ts new file mode 100644 index 0000000000..855a4bcb7e --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/ContainerContract.ts @@ -0,0 +1,40 @@ +import { AleneOmOmsorgenProps } from './AleneOmOmsorgenProps'; +import Komponenter from './Komponenter'; +import { KorrigerePerioderProps } from './KorrigerePerioderProps'; +import { OmsorgProps } from './OmsorgProps'; +import { VilkarKroniskSyktBarnProps } from './VilkarKroniskSyktBarnProps'; +import { VilkarMidlertidigAleneProps } from './VilkarMidlertidigAleneProps'; + +interface KorrigerePerioderContract { + visKomponent: Komponenter.KORRIGERE_PERIODER; + props: KorrigerePerioderProps; +} + +interface VilkarMidlertidigAleneContract { + visKomponent: Komponenter.VILKAR_MIDLERTIDIG_ALENE; + props: VilkarMidlertidigAleneProps; +} + +interface VilkarKroniskSyktBarnContract { + visKomponent: Komponenter.VILKAR_KRONISK_SYKT_BARN; + props: VilkarKroniskSyktBarnProps; +} + +interface OmsorgContract { + visKomponent: Komponenter.OMSORG; + props: OmsorgProps; +} + +interface AleneOmOmsorgenContract { + visKomponent: Komponenter.ALENE_OM_OMSORGEN; + props: AleneOmOmsorgenProps; +} + +type ContainerContract = + | KorrigerePerioderContract + | VilkarMidlertidigAleneContract + | VilkarKroniskSyktBarnContract + | AleneOmOmsorgenContract + | OmsorgContract; + +export default ContainerContract; diff --git a/packages/fakta-omsorgsdager/src/types/FormState.ts b/packages/fakta-omsorgsdager/src/types/FormState.ts new file mode 100644 index 0000000000..c7b2144dbe --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/FormState.ts @@ -0,0 +1,5 @@ +export interface FormState { + getState: (string) => string; + deleteState: (string) => void; + setState: (string, object) => void; +} diff --git a/packages/fakta-omsorgsdager/src/types/InformasjonOmVurdertVilkar.ts b/packages/fakta-omsorgsdager/src/types/InformasjonOmVurdertVilkar.ts new file mode 100644 index 0000000000..e003a7ec1e --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/InformasjonOmVurdertVilkar.ts @@ -0,0 +1,7 @@ +export interface InformasjonOmVurdertVilkar { + begrunnelse: string; + navnPåAksjonspunkt: string; + vilkarOppfylt: boolean; + vilkar: string; + periode?: string; +} diff --git a/packages/fakta-omsorgsdager/src/types/Komponenter.ts b/packages/fakta-omsorgsdager/src/types/Komponenter.ts new file mode 100644 index 0000000000..33b2bebb4a --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/Komponenter.ts @@ -0,0 +1,9 @@ +enum Komponenter { + KORRIGERE_PERIODER = 'KorrigerePerioder', + VILKAR_KRONISK_SYKT_BARN = 'VilkarKroniskSyktBarn', + VILKAR_MIDLERTIDIG_ALENE = 'VilkarMidlertidigAlene', + OMSORG = 'Omsorg', + ALENE_OM_OMSORGEN = 'AleneOmOmsorgen', +} + +export default Komponenter; diff --git a/packages/fakta-omsorgsdager/src/types/KorrigerePerioderProps.ts b/packages/fakta-omsorgsdager/src/types/KorrigerePerioderProps.ts new file mode 100644 index 0000000000..db76635215 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/KorrigerePerioderProps.ts @@ -0,0 +1,17 @@ +import { FormState } from './FormState'; + +export interface KorrigerePerioderProps { + behandlingsID: string; + lesemodus: boolean; + aksjonspunktLost: boolean; + informasjonTilLesemodus?: KorrigerePerioderLesemodus; + losAksjonspunkt: (fravaerGrunnetSmittevernhensynEllerStengt, begrunnelse, antallDagerDelvisInnvilget) => void; + konfliktMedArbeidsgiver: boolean; + formState: FormState; +} + +export interface KorrigerePerioderLesemodus { + begrunnelse: string; + vilkarOppfylt: boolean; + antallDagerDelvisInnvilget: number; +} diff --git a/packages/fakta-omsorgsdager/src/types/OmsorgProps.ts b/packages/fakta-omsorgsdager/src/types/OmsorgProps.ts new file mode 100644 index 0000000000..177f9de1d9 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/OmsorgProps.ts @@ -0,0 +1,17 @@ +import { FormState } from './FormState'; +import { InformasjonOmVurdertVilkar } from './InformasjonOmVurdertVilkar'; +import { InformasjonTilLesemodus } from './informasjonTilLesemodus'; + +export interface OmsorgProps { + behandlingsID: string; + fagytelseType: string; + lesemodus?: boolean; + aksjonspunktLost: boolean; + informasjonTilLesemodus?: InformasjonTilLesemodus; + vedtakFattetVilkarOppfylt: boolean; + informasjonOmVilkar?: InformasjonOmVurdertVilkar; + barn: string[]; + harBarnSoktForRammevedtakOmKroniskSyk: boolean; + losAksjonspunkt?: (harOmsorgen, begrunnelse) => void; + formState: FormState; +} diff --git a/packages/fakta-omsorgsdager/src/types/Omsorgsinfo.ts b/packages/fakta-omsorgsdager/src/types/Omsorgsinfo.ts new file mode 100644 index 0000000000..094d452d33 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/Omsorgsinfo.ts @@ -0,0 +1,3 @@ +export default interface Omsorgsinfo { + harOmsorgen: boolean; +} diff --git a/packages/fakta-omsorgsdager/src/types/Period.ts b/packages/fakta-omsorgsdager/src/types/Period.ts new file mode 100644 index 0000000000..999d46f283 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/Period.ts @@ -0,0 +1,54 @@ +import dayjs from 'dayjs'; + +export class Period { + fom: string; + + tom: string; + + constructor(fom: string, tom: string) { + this.fom = fom; + this.tom = tom; + } + + dateFromString(dateString: string) { + return dayjs(dateString, ['YYYY-MM-DD', 'DD.MM.YYYY']).utc(true); + } + + includesDate(dateString: string) { + const dateInQuestion = this.dateFromString(dateString); + const fomDayjs = this.dateFromString(this.fom); + const tomDayjs = this.dateFromString(this.tom); + return ( + (dateInQuestion.isSame(fomDayjs) || dateInQuestion.isAfter(fomDayjs)) && + (dateInQuestion.isSame(tomDayjs) || dateInQuestion.isBefore(tomDayjs)) + ); + } + + covers(otherPeriod: Period) { + return this.includesDate(otherPeriod.fom) && this.includesDate(otherPeriod.tom); + } + + overlapsLeft(otherPeriod: Period) { + return this.includesDate(otherPeriod.fom) && !this.includesDate(otherPeriod.tom); + } + + overlapsRight(otherPeriod) { + return this.includesDate(otherPeriod.tom) && !this.includesDate(otherPeriod.fom); + } + + overlapsWith(otherPeriod) { + return this.covers(otherPeriod) || this.overlapsLeft(otherPeriod) || this.overlapsRight(otherPeriod); + } + + startsBefore(otherPeriod: Period) { + const dateInQuestion = this.dateFromString(otherPeriod.fom); + const periodFom = this.dateFromString(this.fom); + return periodFom.isBefore(dateInQuestion); + } + + endsAfter(otherPeriod: Period) { + const dateInQuestion = this.dateFromString(otherPeriod.tom); + const periodTom = this.dateFromString(this.tom); + return periodTom.isAfter(dateInQuestion); + } +} diff --git "a/packages/fakta-omsorgsdager/src/types/Tilgjengelige\303\205rOptions.ts" "b/packages/fakta-omsorgsdager/src/types/Tilgjengelige\303\205rOptions.ts" new file mode 100644 index 0000000000..7fd14f0c38 --- /dev/null +++ "b/packages/fakta-omsorgsdager/src/types/Tilgjengelige\303\205rOptions.ts" @@ -0,0 +1,5 @@ +export default interface TilgjengeligÅrOption { + value: string; + title: string; + disabled: boolean; +} diff --git a/packages/fakta-omsorgsdager/src/types/Vedtaksstatus.ts b/packages/fakta-omsorgsdager/src/types/Vedtaksstatus.ts new file mode 100644 index 0000000000..2333ceca65 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/Vedtaksstatus.ts @@ -0,0 +1,8 @@ +enum Vedtaksstatus { + FORESLÅTT = 'FORESLÅTT', + INNVILGET = 'INNVILGET', + AVSLÅTT = 'AVSLÅTT', + FORKASTET = 'FORKASTET', +} + +export default Vedtaksstatus; diff --git a/packages/fakta-omsorgsdager/src/types/VilkarKroniskSyktBarnProps.ts b/packages/fakta-omsorgsdager/src/types/VilkarKroniskSyktBarnProps.ts new file mode 100644 index 0000000000..b2fd59bdb1 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/VilkarKroniskSyktBarnProps.ts @@ -0,0 +1,26 @@ +import { FormState } from './FormState'; +import { InformasjonOmVurdertVilkar } from './InformasjonOmVurdertVilkar'; + +export interface VilkarKroniskSyktBarnProps { + behandlingsID: string; + vedtakFattetVilkarOppfylt: boolean; + soknadsdato: string; + aksjonspunktLost: boolean; + informasjonOmVilkar?: InformasjonOmVurdertVilkar; + lesemodus: boolean; + informasjonTilLesemodus?: InformasjonTilLesemodusKroniskSyk; + losAksjonspunkt: ( + endreHarDokumentasjonOgFravaerRisiko: boolean, + begrunnelse: string, + avslagsårsakKode: string, + fraDato: string, + ) => void; + formState: FormState; +} + +export interface InformasjonTilLesemodusKroniskSyk { + begrunnelse: string; + vilkarOppfylt: boolean; + avslagsårsakKode: string; + fraDato: string; +} diff --git a/packages/fakta-omsorgsdager/src/types/VilkarMidlertidigAleneProps.ts b/packages/fakta-omsorgsdager/src/types/VilkarMidlertidigAleneProps.ts new file mode 100644 index 0000000000..3dc1d7f09d --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/VilkarMidlertidigAleneProps.ts @@ -0,0 +1,33 @@ +import { FormState } from './FormState'; +import { InformasjonOmVurdertVilkar } from './InformasjonOmVurdertVilkar'; + +export interface VilkarMidlertidigAleneProps { + behandlingsID: string; + lesemodus: boolean; + aksjonspunktLost: boolean; + soknadsopplysninger: VilkarMidlertidigSoknadsopplysninger; + informasjonTilLesemodus?: VilkarMidlertidigInformasjonTilLesemodus; + vedtakFattetVilkarOppfylt: boolean; + informasjonOmVilkar?: InformasjonOmVurdertVilkar; + losAksjonspunkt: (VilkarMidlertidigGrunnlagForBeslutt) => void; + formState: FormState; +} + +export interface VilkarMidlertidigAleneDato { + til: string; + fra: string; +} + +export interface VilkarMidlertidigSoknadsopplysninger { + årsak: string; + beskrivelse?: string; + periode: string; + soknadsdato: string; +} + +export interface VilkarMidlertidigInformasjonTilLesemodus { + begrunnelse: string; + vilkarOppfylt: boolean; + dato: VilkarMidlertidigAleneDato; + avslagsArsakErPeriodeErIkkeOverSeksMån?: boolean; +} diff --git a/packages/fakta-omsorgsdager/src/types/Visningsstatus.ts b/packages/fakta-omsorgsdager/src/types/Visningsstatus.ts new file mode 100644 index 0000000000..2eccdd6891 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/Visningsstatus.ts @@ -0,0 +1,5 @@ +export enum Visningsstatus { + SPINNER, + FEIL, + UTEN_FEIL, +} diff --git a/packages/fakta-omsorgsdager/src/types/informasjonTilLesemodus.ts b/packages/fakta-omsorgsdager/src/types/informasjonTilLesemodus.ts new file mode 100644 index 0000000000..6ce1892894 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/types/informasjonTilLesemodus.ts @@ -0,0 +1,4 @@ +export interface InformasjonTilLesemodus { + begrunnelse: string; + vilkarOppfylt: boolean; +} diff --git a/packages/fakta-omsorgsdager/src/ui/MainComponent.tsx b/packages/fakta-omsorgsdager/src/ui/MainComponent.tsx new file mode 100644 index 0000000000..0635cad0ce --- /dev/null +++ b/packages/fakta-omsorgsdager/src/ui/MainComponent.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { AlertStripeFeil } from 'nav-frontend-alertstriper'; +import ContainerContract from '../types/ContainerContract'; +import Komponenter from '../types/Komponenter'; +import KorrigerePerioder from './components/korrigere-perioder/KorrigerePerioder'; +import Omsorg from './components/omsorg/Omsorg'; +import VilkarKroniskSyktBarn from './components/vilkar-kronisk-sykt-barn/VilkarKroniskSyktBarn'; +import AleneOmOmsorgen from './components/alene-om-omsorgen/AleneOmOmsorgen'; +import VilkarMidlertidigAlene from './components/vilkar-midlertidig-alene/VilkarMidlertidigAlene'; +import ContainerContext from './context/ContainerContext'; +import styles from './global.css'; + +interface MainComponentProps { + containerData: ContainerContract; +} + +const MainComponent = ({ containerData }: MainComponentProps): JSX.Element => { + let innhold; + switch (containerData.visKomponent) { + case Komponenter.KORRIGERE_PERIODER: + innhold = ; + break; + case Komponenter.VILKAR_KRONISK_SYKT_BARN: + innhold = ; + break; + case Komponenter.VILKAR_MIDLERTIDIG_ALENE: + innhold = ; + break; + case Komponenter.OMSORG: + innhold = ; + break; + case Komponenter.ALENE_OM_OMSORGEN: + innhold = ; + break; + default: + innhold = Noe gikk galt, vennligst prøv igjen senere; + } + + return ( + +
    {innhold}
    +
    + ); +}; + +export default MainComponent; diff --git a/packages/fakta-omsorgsdager/src/ui/components/aksjonspunkt-lesemodus/AksjonspunktLesemodus.tsx b/packages/fakta-omsorgsdager/src/ui/components/aksjonspunkt-lesemodus/AksjonspunktLesemodus.tsx new file mode 100644 index 0000000000..68468d82d7 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/ui/components/aksjonspunkt-lesemodus/AksjonspunktLesemodus.tsx @@ -0,0 +1,42 @@ +import Lenke from 'nav-frontend-lenker'; +import React from 'react'; +import styleLesemodus from '../lesemodus/lesemodusboks.css'; + +interface OwnProps { + harAksjonspunktBlivitLostTidligare: boolean; + åpneForRedigereInformasjon: () => void; + aksjonspunktTekst: string; +} + +const AksjonspunktLesemodus = ({ + harAksjonspunktBlivitLostTidligare, + åpneForRedigereInformasjon, + aksjonspunktTekst, +}: OwnProps) => { + const håndtereKlikk = e => { + e.preventDefault(); + e.stopPropagation(); + åpneForRedigereInformasjon(); + }; + + return ( +
    +

    + Behandlet aksjonspunkt: {aksjonspunktTekst} +

    + {harAksjonspunktBlivitLostTidligare && ( +
    + { + håndtereKlikk(e); + }} + > + Rediger vurdering + +
    + )} +
    + ); +}; +export default AksjonspunktLesemodus; diff --git a/packages/fakta-omsorgsdager/src/ui/components/alene-om-omsorgen-lesemodus/AleneOmOmsorgenLesemodus.tsx b/packages/fakta-omsorgsdager/src/ui/components/alene-om-omsorgen-lesemodus/AleneOmOmsorgenLesemodus.tsx new file mode 100644 index 0000000000..9d0392aa46 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/ui/components/alene-om-omsorgen-lesemodus/AleneOmOmsorgenLesemodus.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { AleneOmOmsorgenAksjonspunktObjekt } from '../../../types/AleneOmOmsorgenProps'; +import { formatereDatoTilLesemodus } from '../../../util/stringUtils'; +import AksjonspunktLesemodus from '../aksjonspunkt-lesemodus/AksjonspunktLesemodus'; +import OpplysningerFraVedtak from '../opplysninger-fra-vedtak/OpplysningerFraVedtak'; +import OpplysningerFraSoknad from '../opplysninger-fra-soknad/OpplysningerFraSoknad'; +import tekst from '../alene-om-omsorgen/alene-om-omsorgen-tekst'; + +interface OwnProps { + fraDatoFraSoknad: string; + informasjonTilLesemodus: AleneOmOmsorgenAksjonspunktObjekt; + harAksjonspunktBlivitLostTidligare: boolean; + åpneForRedigereInformasjon: () => void; + erBehandlingstypeRevurdering: boolean; +} + +const AleneOmOmsorgenLesemodus: React.FunctionComponent = ({ + fraDatoFraSoknad, + informasjonTilLesemodus, + harAksjonspunktBlivitLostTidligare, + åpneForRedigereInformasjon, + erBehandlingstypeRevurdering, +}) => ( + <> + + + + + + +); +export default AleneOmOmsorgenLesemodus; diff --git a/packages/fakta-omsorgsdager/src/ui/components/alene-om-omsorgen/AleneOmOmsorgen.tsx b/packages/fakta-omsorgsdager/src/ui/components/alene-om-omsorgen/AleneOmOmsorgen.tsx new file mode 100644 index 0000000000..f051906ae2 --- /dev/null +++ b/packages/fakta-omsorgsdager/src/ui/components/alene-om-omsorgen/AleneOmOmsorgen.tsx @@ -0,0 +1,235 @@ +import React, { useEffect } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import classNames from 'classnames'; +import dayjs from 'dayjs'; + +import { Label } from '@navikt/ds-react'; +import { Hovedknapp } from 'nav-frontend-knapper'; +import { RadioGruppe, Select, SkjemaGruppe } from 'nav-frontend-skjema'; + +import useFormSessionStorage from '../../../util/useFormSessionStorageUtils'; +import { valideringsFunksjoner } from '../../../util/validationReactHookFormUtils'; +import AleneOmOmsorgenLesemodus from '../alene-om-omsorgen-lesemodus/AleneOmOmsorgenLesemodus'; +import AlertStripeTrekantVarsel from '../alertstripe-trekant-varsel/AlertStripeTrekantVarsel'; +import styleLesemodus from '../lesemodus/lesemodusboks.css'; +import OpplysningerFraSoknad from '../opplysninger-fra-soknad/OpplysningerFraSoknad'; +import DatePicker from '../react-hook-form-wrappers/DatePicker'; +import RadioButtonWithBooleanValue from '../react-hook-form-wrappers/RadioButton'; +import TextArea from '../react-hook-form-wrappers/TextArea'; +import VilkarStatus from '../vilkar-status/VilkarStatus'; +import tekst from './alene-om-omsorgen-tekst'; +import { + booleanTilTekst, + formatereDato, + formatereDatoTilLesemodus, + tekstTilBoolean, + utledTilgjengeligeÅr, +} from '../../../util/stringUtils'; +import { AleneOmOmsorgenProps } from '../../../types/AleneOmOmsorgenProps'; + +import styles from '../vilkar-midlertidig-alene/vilkarMidlertidigAlene.css'; +import styleRadioknapper from '../styles/radioknapper/radioknapper.css'; + +type FormData = { + begrunnelse: string; + fraDato: string; + tilDato: string; + erSokerenAleneOmOmsorgen: string; + åpenForRedigering: boolean; +}; + +const AleneOmOmsorgen: React.FunctionComponent = ({ + behandlingsID, + aksjonspunktLost, + lesemodus, + fraDatoFraSoknad, + informasjonTilLesemodus, + vedtakFattetVilkarOppfylt, + erBehandlingstypeRevurdering, + informasjonOmVilkar, + losAksjonspunkt, + formState, +}) => { + const formStateKey = `${behandlingsID}-utvidetrett-alene-om-omsorgen`; + const harAksjonspunktOgVilkarLostTidligere = + informasjonTilLesemodus?.fraDato.length > 0 && informasjonTilLesemodus?.begrunnelse.length > 0; + + const methods = useForm({ + reValidateMode: 'onSubmit', + defaultValues: { + begrunnelse: harAksjonspunktOgVilkarLostTidligere ? informasjonTilLesemodus.begrunnelse : '', + fraDato: harAksjonspunktOgVilkarLostTidligere ? formatereDato(informasjonTilLesemodus.fraDato) : 'dd.mm.åååå', + tilDato: harAksjonspunktOgVilkarLostTidligere ? formatereDato(informasjonTilLesemodus.tilDato) : 'dd.mm.åååå', + erSokerenAleneOmOmsorgen: harAksjonspunktOgVilkarLostTidligere + ? booleanTilTekst(informasjonTilLesemodus.vilkarOppfylt) + : '', + åpenForRedigering: false, + }, + }); + + const { + formState: { errors }, + getValues, + handleSubmit, + watch, + setValue, + } = methods; + const erSokerAleneOmOmsorgen = watch('erSokerenAleneOmOmsorgen'); + const åpenForRedigering = watch('åpenForRedigering'); + const tilDatovalue = watch('tilDato'); + + const { erDatoFyltUt, erDatoGyldig } = valideringsFunksjoner(getValues, 'erSokerenAleneOmOmsorgen'); + + useEffect(() => { + if (tekstTilBoolean(erSokerAleneOmOmsorgen)) { + setValue('fraDato', formatereDato(fraDatoFraSoknad)); + } + }, [erSokerAleneOmOmsorgen, fraDatoFraSoknad]); + + const settTilDatoFraÅr = (e: any) => { + setValue('tilDato', e.target.value === 'false' ? 'false' : `${e.target.value}.12.31`); + }; + + const mellomlagringFormState = useFormSessionStorage( + formStateKey, + formState, + methods.watch, + methods.setValue, + lesemodus, + åpenForRedigering, + getValues, + ); + + const bekreftAksjonspunkt = ({ begrunnelse, erSokerenAleneOmOmsorgen, fraDato, tilDato }) => { + if ( + (!errors.begrunnelse && !errors.fraDato && !errors.erSokerenAleneOmOmsorgen && !erBehandlingstypeRevurdering) || + (!errors.begrunnelse && + !errors.fraDato && + !errors.tilDato && + !errors.erSokerenAleneOmOmsorgen && + erBehandlingstypeRevurdering) + ) { + losAksjonspunkt({ + begrunnelse, + vilkarOppfylt: tekstTilBoolean(erSokerenAleneOmOmsorgen), + fraDato: tekstTilBoolean(erSokerenAleneOmOmsorgen) ? fraDato.replaceAll('.', '-') : '', + tilDato: tilDato.replaceAll('.', '-'), + }); + setValue('åpenForRedigering', false); + mellomlagringFormState.fjerneState(); + } + }; + + return ( +
    + {vedtakFattetVilkarOppfylt && ( + + )} + + {lesemodus && !åpenForRedigering && !vedtakFattetVilkarOppfylt && ( + setValue('åpenForRedigering', true)} + erBehandlingstypeRevurdering={erBehandlingstypeRevurdering} + /> + )} + + {(åpenForRedigering || (!lesemodus && !vedtakFattetVilkarOppfylt)) && ( + <> + + + + + +
    +