From 65694178fa17b167433ee14d8857ef5133197123 Mon Sep 17 00:00:00 2001 From: Hallvard Andreas Stark <57254397+hallvardastark@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:44:56 +0100 Subject: [PATCH] =?UTF-8?q?Lager=20prosess-s=C3=B8knadsfrist=20i=20v2-vers?= =?UTF-8?q?jon.=20(#6722)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Lager prosess-søknadsfrist i v2-versjon. * Rydding * Legger inn bruk av object-hash igjen * Flytter tester inn i story * rydding * rydding * Bruker panel for ungdomsytelse * Fiks imports * Fikset diverse --- package.json | 1 + .../SoknadsfristPanelDef.tsx | 12 +- .../src/k9sak/kodeverk/AksjonspunktType.ts | 10 + .../src/k9sak/kodeverk/KravDokumentStatus.ts | 10 + .../kodeverk/behandling/Vilk\303\245rType.ts" | 42 ++ packages/v2/gui/package.json | 7 +- .../SoknadsfristVilkarProsessIndex.module.css | 20 + ...adsfristVilkarProsessIndex.module.d.css.ts | 8 + ...SoknadsfristVilkarProsessIndex.stories.tsx | 501 ++++++++++++++++++ .../SoknadsfristVilkarProsessIndex.tsx | 201 +++++++ .../components/FormState.ts | 11 + .../components/OverstyrBekreftKnappPanel.tsx | 21 + .../SoknadsfristVilkarDokument.module.css | 20 + ...SoknadsfristVilkarDokument.module.d.css.ts | 8 + .../components/SoknadsfristVilkarDokument.tsx | 192 +++++++ .../SoknadsfristVilkarForm.module.css | 27 + .../SoknadsfristVilkarForm.module.d.css.ts | 9 + .../components/SoknadsfristVilkarForm.tsx | 326 ++++++++++++ .../components/SoknadsfristVilkarHeader.tsx | 118 +++++ .../types/KravDokumentStatus.ts | 20 + .../types/SoknadsfristAksjonspunktType.ts | 10 + .../types/SoknadsfristVilkarType.ts | 3 + .../types/S\303\270knadsfristPeriode.ts" | 9 + .../types/S\303\270knadsfristTilstand.ts" | 5 + .../types/submitCallback.ts | 4 + .../prosess/vilkar-soknadsfrist/utils.spec.ts | 130 +++++ .../src/prosess/vilkar-soknadsfrist/utils.ts | 48 ++ .../src/shared/hook-form/RadioGroupPanel.tsx | 80 +++ .../v2/gui/src/shared/hook-form/formUtils.ts | 4 + yarn.lock | 201 +++---- 30 files changed, 1954 insertions(+), 104 deletions(-) create mode 100644 packages/v2/backend/src/k9sak/kodeverk/AksjonspunktType.ts create mode 100644 packages/v2/backend/src/k9sak/kodeverk/KravDokumentStatus.ts create mode 100644 "packages/v2/backend/src/k9sak/kodeverk/behandling/Vilk\303\245rType.ts" create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.module.css create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.module.d.css.ts create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.stories.tsx create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.tsx create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/FormState.ts create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/OverstyrBekreftKnappPanel.tsx create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.module.css create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.module.d.css.ts create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.tsx create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.module.css create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.module.d.css.ts create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.tsx create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarHeader.tsx create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/KravDokumentStatus.ts create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/SoknadsfristAksjonspunktType.ts create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/SoknadsfristVilkarType.ts create mode 100644 "packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/S\303\270knadsfristPeriode.ts" create mode 100644 "packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/S\303\270knadsfristTilstand.ts" create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/submitCallback.ts create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/utils.spec.ts create mode 100644 packages/v2/gui/src/prosess/vilkar-soknadsfrist/utils.ts create mode 100644 packages/v2/gui/src/shared/hook-form/RadioGroupPanel.tsx create mode 100644 packages/v2/gui/src/shared/hook-form/formUtils.ts diff --git a/package.json b/package.json index 5fd1060c87..349ac69ad5 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "@testing-library/react": "16.0.1", "@testing-library/user-event": "14.5.2", "@types/history": "5.0.0", + "@types/object-hash": "^3.0.6", "@types/prop-types": "15.7.13", "@types/react": "18.3.12", "@types/react-collapse": "5.0.4", diff --git a/packages/behandling-ungdomsytelse/src/panelDefinisjoner/prosessStegPaneler/inngangsvilkarPaneler/SoknadsfristPanelDef.tsx b/packages/behandling-ungdomsytelse/src/panelDefinisjoner/prosessStegPaneler/inngangsvilkarPaneler/SoknadsfristPanelDef.tsx index ca0f02c767..04605db802 100644 --- a/packages/behandling-ungdomsytelse/src/panelDefinisjoner/prosessStegPaneler/inngangsvilkarPaneler/SoknadsfristPanelDef.tsx +++ b/packages/behandling-ungdomsytelse/src/panelDefinisjoner/prosessStegPaneler/inngangsvilkarPaneler/SoknadsfristPanelDef.tsx @@ -3,9 +3,8 @@ import behandlingStatus from '@fpsak-frontend/kodeverk/src/behandlingStatus'; import kodeverkTyper from '@fpsak-frontend/kodeverk/src/kodeverkTyper'; import vilkarType from '@fpsak-frontend/kodeverk/src/vilkarType'; import { ProsessStegPanelDef } from '@k9-sak-web/behandling-felles'; +import SoknadsfristVilkarProsessIndex from '@k9-sak-web/gui/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.js'; import { konverterKodeverkTilKode } from '@k9-sak-web/lib/kodeverk/konverterKodeverkTilKode.js'; -import SoknadsfristVilkarProsessIndex from '@k9-sak-web/prosess-vilkar-soknadsfrist'; -import SoknadsfristVilkarProsessIndexV2 from '@k9-sak-web/prosess-vilkar-soknadsfrist-v2'; import { UngdomsytelseBehandlingApiKeys } from '../../../data/ungdomsytelseBehandlingApi'; class SoknadsfristPanelDef extends ProsessStegPanelDef { @@ -14,12 +13,9 @@ class SoknadsfristPanelDef extends ProsessStegPanelDef { getTekstKode = () => 'Søknadsfrist'; getKomponent = props => { - if (props.featureToggles?.PROSESS_VILKAR_SOKNADSFRIST) { - const deepCopyProps = JSON.parse(JSON.stringify(props)); - konverterKodeverkTilKode(deepCopyProps, false); - return ; - } - return ; + const deepCopyProps = JSON.parse(JSON.stringify(props)); + konverterKodeverkTilKode(deepCopyProps, false); + return ; }; getAksjonspunktKoder = () => [ diff --git a/packages/v2/backend/src/k9sak/kodeverk/AksjonspunktType.ts b/packages/v2/backend/src/k9sak/kodeverk/AksjonspunktType.ts new file mode 100644 index 0000000000..d08c980bf9 --- /dev/null +++ b/packages/v2/backend/src/k9sak/kodeverk/AksjonspunktType.ts @@ -0,0 +1,10 @@ +import type { AksjonspunktDto } from '@navikt/k9-sak-typescript-client'; + +export type AksjonspunktType = NonNullable; +type AksjonspunktTypeName = 'MANUELL' | 'AUTOPUNKT' | 'OVERSTYRING' | 'SAKSBEHANDLEROVERSTYRING'; +export const aksjonspunktType: Readonly> = { + MANUELL: 'MANU', + AUTOPUNKT: 'AUTO', + OVERSTYRING: 'OVST', + SAKSBEHANDLEROVERSTYRING: 'SAOV', +}; diff --git a/packages/v2/backend/src/k9sak/kodeverk/KravDokumentStatus.ts b/packages/v2/backend/src/k9sak/kodeverk/KravDokumentStatus.ts new file mode 100644 index 0000000000..bd7478087d --- /dev/null +++ b/packages/v2/backend/src/k9sak/kodeverk/KravDokumentStatus.ts @@ -0,0 +1,10 @@ +import type { KravDokumentMedSøktePerioder } from '@navikt/k9-sak-typescript-client'; + +export type KravDokumentStatusType = KravDokumentMedSøktePerioder['type']; + +export const kravDokumentStatusType: Readonly< + Record +> = { + INNTEKTSMELDING: 'INNTEKTSMELDING', + SØKNAD: 'SØKNAD', +}; diff --git "a/packages/v2/backend/src/k9sak/kodeverk/behandling/Vilk\303\245rType.ts" "b/packages/v2/backend/src/k9sak/kodeverk/behandling/Vilk\303\245rType.ts" new file mode 100644 index 0000000000..a6412e1185 --- /dev/null +++ "b/packages/v2/backend/src/k9sak/kodeverk/behandling/Vilk\303\245rType.ts" @@ -0,0 +1,42 @@ +type VilkarTypeName = + | 'OMSORGENFORVILKARET' + | 'MEDLEMSKAPSVILKARET' + | 'MEDISINSKEVILKÅR_UNDER_18_ÅR' + | 'MEDISINSKEVILKÅR_18_ÅR' + | 'ALDERSVILKARET' + | 'SOKNADSFRISTVILKARET' + | 'OPPTJENINGSVILKARET' + | 'SOKERSOPPLYSNINGSPLIKT' + | 'BEREGNINGSGRUNNLAGVILKARET' + | 'UNNTAKSVILKARET' + | 'UTVIDETRETTVILKARET' + | 'OMP_OMSORGENFORVILKARET' + | 'PLEIEPENGER_LIVETS_SLUTTFASE' + | 'ALDERSVILKAR_BARN' + | 'LANGVARIG_SYKDOM' + | 'NØDVENDIG_OPPLÆRING' + | 'GODKJENT_OPPLÆRINGSINSTITUSJON' + | 'GJENNOMGÅ_OPPLÆRING' + | 'UNGDOMSPROGRAMVILKARET'; + +export const vilkarType: Readonly> = { + OMSORGENFORVILKARET: 'K9_VK_1', + MEDLEMSKAPSVILKARET: 'FP_VK_2', + MEDISINSKEVILKÅR_UNDER_18_ÅR: 'K9_VK_2_a', + MEDISINSKEVILKÅR_18_ÅR: 'K9_VK_2_b', + ALDERSVILKARET: 'K9_VK_3', + SOKNADSFRISTVILKARET: 'FP_VK_3', + OPPTJENINGSVILKARET: 'FP_VK_23', + SOKERSOPPLYSNINGSPLIKT: 'FP_VK_34', + BEREGNINGSGRUNNLAGVILKARET: 'FP_VK_41', + UNNTAKSVILKARET: 'K9_VILKÅRET', + UTVIDETRETTVILKARET: 'K9_VK_9_6', + OMP_OMSORGENFORVILKARET: 'K9_VK_1', + PLEIEPENGER_LIVETS_SLUTTFASE: 'K9_VK_16', + ALDERSVILKAR_BARN: 'K9_VK_5_3', + LANGVARIG_SYKDOM: 'K9_VK_17', + NØDVENDIG_OPPLÆRING: 'K9_VK_20', + GODKJENT_OPPLÆRINGSINSTITUSJON: 'K9_VK_21', + GJENNOMGÅ_OPPLÆRING: 'K9_VK_22', + UNGDOMSPROGRAMVILKARET: 'UNG_VK_XXX', +}; diff --git a/packages/v2/gui/package.json b/packages/v2/gui/package.json index 660c9e276d..a9c48bcc1e 100644 --- a/packages/v2/gui/package.json +++ b/packages/v2/gui/package.json @@ -14,12 +14,15 @@ "./prosess/*": "./src/prosess/*" }, "dependencies": { + "@hookform/error-message": "^2.0.1", "@k9-sak-web/backend": "workspace:^", "@k9-sak-web/lib": "1.0.0", - "@navikt/ft-form-validators": "2.4.1", - "@navikt/ft-utils": "2.4.0", + "@navikt/ft-form-validators": "2.4.5", + "@navikt/ft-utils": "2.4.5", "@tanstack/react-query": "^5.59.19", + "@types/object-hash": "^3.0.6", "axios": "1.7.7", + "object-hash": "^3.0.0", "react": "18.3.1" } } diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.module.css b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.module.css new file mode 100644 index 0000000000..fc996940a3 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.module.css @@ -0,0 +1,20 @@ +.contentContainer { + flex: 1; +} + +.sideMenuContainer { + margin: 0 32px 0 0; +} + +.sideMenuContainer > div { + height: auto; +} + +.mainContainerWithSideMenu { + display: flex; + margin-left: -32px; +} + +.warningIcon { + margin-left: 1rem; +} diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.module.d.css.ts b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.module.d.css.ts new file mode 100644 index 0000000000..848399dafa --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.module.d.css.ts @@ -0,0 +1,8 @@ +declare const styles: { + readonly "contentContainer": string; + readonly "mainContainerWithSideMenu": string; + readonly "sideMenuContainer": string; + readonly "warningIcon": string; +}; +export = styles; + diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.stories.tsx b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.stories.tsx new file mode 100644 index 0000000000..a253eba4c0 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.stories.tsx @@ -0,0 +1,501 @@ +import React from 'react'; + +import { aksjonspunktStatus } from '@k9-sak-web/backend/k9sak/kodeverk/AksjonspunktStatus.js'; +import { aksjonspunktType } from '@k9-sak-web/backend/k9sak/kodeverk/AksjonspunktType.js'; +import { vilkårStatus } from '@k9-sak-web/backend/k9sak/kodeverk/behandling/VilkårStatus.js'; +import { vilkarType } from '@k9-sak-web/backend/k9sak/kodeverk/behandling/VilkårType.js'; +import { kravDokumentStatusType } from '@k9-sak-web/backend/k9sak/kodeverk/KravDokumentStatus.js'; +import { action } from '@storybook/addon-actions'; +import type { Meta, StoryObj } from '@storybook/react'; +import { expect, fn, userEvent, waitFor } from '@storybook/test'; +import SoknadsfristVilkarProsessIndex from './SoknadsfristVilkarProsessIndex'; + +const vilkarSoknadsfrist = [ + { + vilkarType: vilkarType.SOKNADSFRISTVILKARET, // kodeverk: 'test' + overstyrbar: true, + perioder: [ + { + vilkarStatus: vilkårStatus.IKKE_OPPFYLT, // kodeverk: 'test' + vurderesIBehandlingen: true, + periode: { + fom: '2020-02-20', + tom: '2020-02-25', + }, + }, + { + vilkarStatus: vilkårStatus.IKKE_OPPFYLT, // kodeverk: 'test' + vurderesIBehandlingen: true, + periode: { + fom: '2020-02-26', + tom: '2020-02-27', + }, + }, + { + vilkarStatus: vilkårStatus.OPPFYLT, // kodeverk: 'test' + vurderesIBehandlingen: true, + periode: { + fom: '2020-02-28', + tom: '2020-02-29', + }, + }, + ], + }, +]; + +const soknadsStatus = { + dokumentStatus: [ + { + type: kravDokumentStatusType.SØKNAD, // kodeverk: 'test' + status: [ + { + periode: { fom: '2020-02-20', tom: '2020-02-25' }, + status: vilkårStatus.IKKE_OPPFYLT, // kodeverk: 'test' + }, + ], + innsendingstidspunkt: '2020-06-01', + journalpostId: '12345', + }, + { + type: kravDokumentStatusType.SØKNAD, // kodeverk: 'test' + status: [ + { + periode: { fom: '2020-02-26', tom: '2020-02-27' }, + status: vilkårStatus.IKKE_OPPFYLT, // kodeverk: 'test' + }, + ], + innsendingstidspunkt: '2020-06-01', + journalpostId: '23456', + }, + ], +}; + +const meta: Meta = { + title: 'gui/prosess/vilkar-soknadsfrist', + component: SoknadsfristVilkarProsessIndex, +}; + +type Story = StoryObj; + +const defaultArgs = { + overrideReadOnly: false, + kanOverstyreAccess: { + employeeHasAccess: true, + isEnabled: true, + }, + panelTittelKode: 'Søknadsfrist', + kanEndrePåSøknadsopplysninger: true, +}; + +export const visOverstyringspanelForSoknadsfrist: Story = { + args: { + ...defaultArgs, + }, + play: async ({ canvas, step }) => { + await step('skal vise overskrift og lovparagraf', async () => { + await userEvent.click(canvas.getByText('26.02.2020 - 27.02.2020')); + await userEvent.click(canvas.getByRole('button', { name: 'Overstyring av søknadsfristvilkåret' })); + expect( + canvas.getAllByText( + (_, element) => element?.textContent === 'SØKNAD innsendt 01.06.2020 (journalpostId: 23456)', + )[0], + ).toBeInTheDocument(); + expect(canvas.getAllByText('Vilkåret er oppfylt for hele perioden').length).toBe(2); + expect(canvas.getByRole('button', { name: 'Bekreft overstyring' })).toBeInTheDocument(); + }); + }, + render: props => { + const [erOverstyrt, toggleOverstyring] = React.useState(false); + return ( + toggleOverstyring(!erOverstyrt)} + erOverstyrt={erOverstyrt} + soknadsfristStatus={soknadsStatus} + vilkar={vilkarSoknadsfrist} + /> + ); + }, +}; + +export const visOverstyringspanelForSoknadsfristUtenDokumenter: Story = { + args: { + ...defaultArgs, + }, + render: props => { + const [erOverstyrt, toggleOverstyring] = React.useState(false); + return ( + toggleOverstyring(!erOverstyrt)} + erOverstyrt={erOverstyrt} + soknadsfristStatus={{ dokumentStatus: [] }} + vilkar={vilkarSoknadsfrist} + /> + ); + }, +}; + +export const VisSoknadsfristAksjonspunkt5077: Story = { + args: { + ...defaultArgs, + submitCallback: fn(), + }, + + play: async ({ canvas, step, args }) => { + await step('skal vise overskrift og lovparagraf', () => { + expect(canvas.getByRole('heading', { name: 'Søknadsfrist' })).toBeInTheDocument(); + expect(canvas.getByText('§')).toBeInTheDocument(); + expect(canvas.getByText('22-13')).toBeInTheDocument(); + }); + + await step('skal formatere data ved innsending ved oppfylt vilkår', async () => { + await userEvent.click(canvas.getByText('28.04.2021 - 30.04.2021')); + await userEvent.click(canvas.getByText('Vilkåret er oppfylt for hele perioden')); + await userEvent.type( + canvas.getByLabelText('Vurder om det har vært fristavbrytende kontakt'), + 'Dette er en begrunnelse', + ); + await waitFor(() => expect(canvas.getByText('Bekreft og gå videre')).toBeEnabled()); + await userEvent.click(canvas.getByText('Bekreft og gå videre')); + await waitFor(() => expect(args.submitCallback).toHaveBeenCalledTimes(1)); + expect(args.submitCallback).toHaveBeenNthCalledWith(1, [ + { + avklarteKrav: [ + { + begrunnelse: 'Dette er en begrunnelse', + erVilkarOk: true, + fraDato: '2021-04-27', + godkjent: true, + journalpostId: '510536417', + }, + ], + begrunnelse: 'Dette er en begrunnelse', + erVilkarOk: true, + kode: '5077', + periode: { + fom: '2021-04-28', + tom: '2021-04-30', + }, + }, + ]); + }); + + await step('skal formatere data ved innsending ved delvis oppfylt vilkår', async () => { + await userEvent.click(canvas.getByText('28.04.2021 - 30.04.2021')); + await userEvent.click(canvas.getByText('Vilkåret er oppfylt for deler av perioden')); + await userEvent.type(canvas.getByLabelText('Oppgi dato søknadsfristvilkåret er oppfylt fra'), '03.05.2021'); + await waitFor(() => expect(canvas.getByText('Bekreft og gå videre')).toBeEnabled()); + await userEvent.click(canvas.getByText('Bekreft og gå videre')); + await waitFor(() => expect(args.submitCallback).toHaveBeenCalledTimes(2)); + expect(args.submitCallback).toHaveBeenNthCalledWith(2, [ + { + avklarteKrav: [ + { + begrunnelse: 'Dette er en begrunnelse', + erVilkarOk: true, + fraDato: '2021-05-02', + godkjent: true, + journalpostId: '510536417', + }, + ], + begrunnelse: 'Dette er en begrunnelse', + erVilkarOk: true, + kode: '5077', + periode: { + fom: '2021-04-28', + tom: '2021-04-30', + }, + }, + ]); + }); + + await step('skal formatere data ved innsending ved ikke oppfylt vilkår', async () => { + await userEvent.click(canvas.getByText('28.04.2021 - 30.04.2021')); + await userEvent.click(canvas.getByLabelText('ikke', { exact: false })); + await waitFor(() => expect(canvas.getByText('Bekreft og gå videre')).toBeEnabled()); + await userEvent.click(canvas.getByText('Bekreft og gå videre')); + await waitFor(() => expect(args.submitCallback).toHaveBeenCalledTimes(3)); + expect(args.submitCallback).toHaveBeenNthCalledWith(3, [ + { + avklarteKrav: [ + { + begrunnelse: 'Dette er en begrunnelse', + erVilkarOk: false, + fraDato: '2021-04-30', + godkjent: false, + journalpostId: '510536417', + }, + ], + begrunnelse: 'Dette er en begrunnelse', + erVilkarOk: false, + kode: '5077', + periode: { + fom: '2021-04-28', + tom: '2021-04-30', + }, + }, + ]); + }); + }, + render: props => { + const [erOverstyrt, toggleOverstyring] = React.useState(false); + return ( + toggleOverstyring(!erOverstyrt)} + erOverstyrt={erOverstyrt} + soknadsfristStatus={{ + dokumentStatus: [ + { + type: 'SØKNAD', + status: [ + { + periode: { fom: '2021-04-28', tom: '2021-04-30' }, + status: 'IKKE_VURDERT', // kodeverk: 'VILKAR_UTFALL_TYPE' + }, + { + periode: { fom: '2021-05-01', tom: '2021-05-05' }, + status: 'OPPFYLT', // kodeverk: 'VILKAR_UTFALL_TYPE' + }, + ], + innsendingstidspunkt: '2021-08-19T11:50:21.894', + journalpostId: '510536417', + }, + ], + }} + vilkar={[ + { + perioder: [ + { + avslagKode: null, + merknadParametere: {}, + vilkarStatus: 'IKKE_VURDERT', // kodeverk: 'VILKAR_UTFALL_TYPE' + periode: { fom: '2021-04-28', tom: '2021-04-30' }, + begrunnelse: null, + vurderesIBehandlingen: true, + }, + { + avslagKode: null, + merknadParametere: {}, + vilkarStatus: 'OPPFYLT', // kodeverk: 'VILKAR_UTFALL_TYPE' + periode: { fom: '2021-05-01', tom: '2021-05-05' }, + begrunnelse: null, + vurderesIBehandlingen: true, + }, + ], + }, + ]} + /> + ); + }, +}; + +export const visSoknadsfristAksjonspunkt5077Delvis: Story = { + args: { + ...defaultArgs, + }, + render: props => { + const [erOverstyrt, toggleOverstyring] = React.useState(false); + return ( + toggleOverstyring(!erOverstyrt)} + erOverstyrt={erOverstyrt} + soknadsfristStatus={{ + dokumentStatus: [ + { + type: 'SØKNAD', + status: [ + { + periode: { fom: '2021-04-26', tom: '2021-04-27' }, + status: 'IKKE_OPPFYLT', // kodeverk: 'VILKAR_UTFALL_TYPE' + }, + { + periode: { fom: '2021-04-28', tom: '2021-05-06' }, + status: 'OPPFYLT', // kodeverk: 'VILKAR_UTFALL_TYPE' + }, + ], + innsendingstidspunkt: '2021-08-26T16:17:16.538', + journalpostId: '510540058', + avklarteOpplysninger: { + godkjent: true, + fraDato: '2021-04-27', + begrunnelse: [ + 'jsdfsdf ljksdlkfj sldjf lsdkjf lsjdf\n\n\n', + 'sdsdfs øjjølksdjfølkjsd fjsd s fløskjdflsjd f\n\n\n', + 'sdklfjsøl jølsdjfø lsjdfljsldøjf sdjf slødjf sld\n\n\n', + 'sldfj sljfølsjd fløsdlfj øsldjf lsøjdfølsdjfløsjd lsdfs', + ].join(''), + opprettetAv: 'saksbeh', + opprettetTidspunkt: '2021-08-26T16:17:16.538', + }, + }, + ], + }} + vilkar={[ + { + perioder: [ + { + avslagKode: '1007', + merknadParametere: {}, + vilkarStatus: 'IKKE_OPPFYLT', // kodeverk: 'VILKAR_UTFALL_TYPE' + periode: { fom: '2021-04-26', tom: '2021-04-27' }, + begrunnelse: null, + vurderesIBehandlingen: true, + }, + { + avslagKode: null, + merknadParametere: {}, + vilkarStatus: 'OPPFYLT', // kodeverk: 'VILKAR_UTFALL_TYPE' + periode: { fom: '2021-04-28', tom: '2021-04-30' }, + begrunnelse: null, + vurderesIBehandlingen: true, + }, + { + avslagKode: null, + merknadParametere: {}, + vilkarStatus: 'OPPFYLT', // kodeverk: 'VILKAR_UTFALL_TYPE' + periode: { fom: '2021-05-01', tom: '2021-05-06' }, + begrunnelse: null, + vurderesIBehandlingen: true, + }, + ], + }, + ]} + /> + ); + }, +}; + +export const VisSoknadsfristAksjonspunkt5077FlereSøknader: Story = { + args: { + ...defaultArgs, + }, + render: props => { + const [erOverstyrt, toggleOverstyring] = React.useState(false); + return ( + toggleOverstyring(!erOverstyrt)} + erOverstyrt={erOverstyrt} + soknadsfristStatus={{ + dokumentStatus: [ + { + type: 'SØKNAD', // kodeverk: 'dokumentStatus' + status: [ + { + periode: { + fom: '2024-03-25', + tom: '2024-03-29', + }, + status: 'IKKE_VURDERT', // kodeverk: 'VILKAR_UTFALL_TYPE' + }, + ], + innsendingstidspunkt: '2024-09-05T13:01:56.322', + journalpostId: '671129216', + }, + { + type: 'SØKNAD', // kodeverk: 'dokumentStatus' + status: [ + { + periode: { + fom: '2024-03-04', + tom: '2024-03-08', + }, + status: 'IKKE_VURDERT', // kodeverk: 'VILKAR_UTFALL_TYPE' + }, + ], + innsendingstidspunkt: '2024-09-05T13:00:44.446', + journalpostId: '671129199', + }, + { + type: 'SØKNAD', + status: [ + { + periode: { + fom: '2024-04-15', + tom: '2024-04-19', + }, + status: 'IKKE_VURDERT', // kodeverk: 'VILKAR_UTFALL_TYPE' + }, + ], + innsendingstidspunkt: '2024-09-05T13:03:07.003', + journalpostId: '671129200', + }, + ], + }} + vilkar={[ + { + lovReferanse: '§ 22-13 tredje ledd', + perioder: [ + { + avslagKode: null, + merknadParametere: {}, + vilkarStatus: 'IKKE_VURDERT', // kodeverk: 'VILKAR_UTFALL_TYPE' + periode: { + fom: '2024-03-04', + tom: '2024-03-08', + }, + begrunnelse: null, + vurderesIBehandlingen: true, + vurdersIBehandlingen: true, + }, + { + avslagKode: null, + merknadParametere: {}, + vilkarStatus: 'IKKE_VURDERT', // kodeverk: 'VILKAR_UTFALL_TYPE' + periode: { + fom: '2024-03-25', + tom: '2024-03-29', + }, + begrunnelse: null, + vurderesIBehandlingen: true, + vurdersIBehandlingen: true, + }, + ], + }, + ]} + /> + ); + }, +}; + +export default meta; diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.tsx b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.tsx new file mode 100644 index 0000000000..b5d1f69cd3 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/SoknadsfristVilkarProsessIndex.tsx @@ -0,0 +1,201 @@ +import { aksjonspunktkodeDefinisjonType } from '@k9-sak-web/backend/k9sak/kodeverk/AksjonspunktkodeDefinisjon.js'; +import { aksjonspunktStatus } from '@k9-sak-web/backend/k9sak/kodeverk/AksjonspunktStatus.js'; +import { vilkårStatus } from '@k9-sak-web/backend/k9sak/kodeverk/behandling/VilkårStatus.js'; +import { initializeDate } from '@k9-sak-web/lib/dateUtils/initializeDate.js'; +import { ExclamationmarkTriangleFillIcon } from '@navikt/aksel-icons'; +import { SideMenu } from '@navikt/ft-plattform-komponenter'; +import { Dayjs } from 'dayjs'; +import { useEffect, useState, type SetStateAction } from 'react'; +import SoknadsfristVilkarForm from './components/SoknadsfristVilkarForm'; +import SoknadsfristVilkarHeader from './components/SoknadsfristVilkarHeader'; +import styles from './SoknadsfristVilkarProsessIndex.module.css'; +import type { SoknadsfristAksjonspunktType } from './types/SoknadsfristAksjonspunktType'; +import type { SoknadsfristVilkarType } from './types/SoknadsfristVilkarType'; +import type { SubmitData } from './types/submitCallback'; +import type { SøknadsfristTilstand } from './types/SøknadsfristTilstand'; +import { formatDate, hentAktivePerioderFraVilkar, utledInnsendtSoknadsfrist } from './utils'; + +const lovReferanse = '§ 22-13'; + +interface SoknadsfristVilkarProsessIndexProps { + aksjonspunkter: SoknadsfristAksjonspunktType[]; + submitCallback: (props: SubmitData[]) => void; + overrideReadOnly: boolean; + kanOverstyreAccess: { + employeeHasAccess: boolean; + isEnabled: boolean; + }; + toggleOverstyring: (overstyrtPanel: SetStateAction) => void; + erOverstyrt: boolean; + panelTittelKode: string; + vilkar: SoknadsfristVilkarType[]; + visAllePerioder: boolean; + soknadsfristStatus: SøknadsfristTilstand; + kanEndrePåSøknadsopplysninger: boolean; +} + +// Finner ut om Statusperiode gjelder for vilkårsperiode +const erRelevantForPeriode = ( + vilkårPeriodeFom: Dayjs | null, + vilkårPeriodeTom: Dayjs | null, + statusPeriodeFom: Dayjs | null, + statusPeriodeTom: Dayjs | null, + innsendtDato?: string | null, +) => { + if (!vilkårPeriodeFom || !vilkårPeriodeTom || !statusPeriodeFom || !statusPeriodeTom || !innsendtDato) { + return false; + } + // er starten av vilkårsperioden før opprinnelig søkndasfrist + const erAktuellForPerioden = utledInnsendtSoknadsfrist(innsendtDato, false) > vilkårPeriodeFom; + // overlapper vilkårsperioden med statusperioden fra dokumentet + const overlapperPerioder = vilkårPeriodeFom <= statusPeriodeTom && vilkårPeriodeTom >= statusPeriodeFom; + return erAktuellForPerioden && overlapperPerioder; +}; + +const SoknadsfristVilkarProsessIndex = ({ + aksjonspunkter, + submitCallback, + overrideReadOnly, + kanOverstyreAccess, + toggleOverstyring, + erOverstyrt, + panelTittelKode, + vilkar, + visAllePerioder, + soknadsfristStatus, + kanEndrePåSøknadsopplysninger, +}: SoknadsfristVilkarProsessIndexProps) => { + const [activeTab, setActiveTab] = useState(0); + const [activeVilkår] = vilkar; + const perioder = hentAktivePerioderFraVilkar(vilkar, visAllePerioder); + useEffect(() => { + if (!visAllePerioder && activeTab >= perioder.length) { + setActiveTab(0); + } + }, [activeTab, visAllePerioder]); + + useEffect(() => { + if (perioder.length > 1) { + const førsteIkkeVurdertPeriodeIndex = perioder.findIndex( + periode => periode.vurderesIBehandlingen && periode.vilkarStatus === vilkårStatus.IKKE_VURDERT, + ); + if (førsteIkkeVurdertPeriodeIndex > 0) { + setActiveTab(førsteIkkeVurdertPeriodeIndex); + } + } + }, []); + + if (perioder.length === 0) { + return null; + } + const activePeriode = perioder.length === 1 ? perioder[0] : perioder[activeTab]; + + const harÅpentUløstAksjonspunkt = aksjonspunkter.some( + ap => + ap.definisjon === aksjonspunktkodeDefinisjonType.KONTROLLER_OPPLYSNINGER_OM_SØKNADSFRIST && + ap.status === aksjonspunktStatus.OPPRETTET && + ap.kanLoses, + ); + + const dokumenterSomSkalVurderes = Array.isArray(soknadsfristStatus?.dokumentStatus) + ? soknadsfristStatus.dokumentStatus.filter(dok => + dok.status?.some(status => { + const erOppfyllt = status.status === vilkårStatus.OPPFYLT; + const avklartEllerOverstyrt = dok.overstyrteOpplysninger || dok.avklarteOpplysninger; + + if (erOppfyllt && !avklartEllerOverstyrt) { + return false; + } + + const statusPeriodeFom = status.periode.fom ? initializeDate(status.periode.fom) : null; + const statusPeriodeTom = status.periode.tom ? initializeDate(status.periode.tom) : null; + + return perioder.some(vilkårPeriode => { + const vilkårPeriodeFom = vilkårPeriode.periode.fom ? initializeDate(vilkårPeriode.periode.fom) : null; + const vilkårPeriodeTom = vilkårPeriode.periode.tom ? initializeDate(vilkårPeriode.periode.tom) : null; + return erRelevantForPeriode( + vilkårPeriodeFom, + vilkårPeriodeTom, + statusPeriodeFom, + statusPeriodeTom, + dok.innsendingstidspunkt, + ); + }); + }), + ) + : []; + + const activePeriodeFom = activePeriode?.periode.fom ? initializeDate(activePeriode.periode.fom) : null; + const activePeriodeTom = activePeriode?.periode.tom ? initializeDate(activePeriode.periode.tom) : null; + + const dokumenterIAktivPeriode = dokumenterSomSkalVurderes.filter(dok => + dok.status?.some(status => { + const statusPeriodeFom = status.periode.fom ? initializeDate(status.periode.fom) : null; + const statusPeriodeTom = status.periode.tom ? initializeDate(status.periode.tom) : null; + return erRelevantForPeriode( + activePeriodeFom, + activePeriodeTom, + statusPeriodeFom, + statusPeriodeTom, + dok.innsendingstidspunkt, + ); + }), + ); + + return ( +
+
+ ({ + active: activeTab === index, + label: + periode.fom && periode.tom + ? `${formatDate(periode.fom)} - ${formatDate(periode.tom)}` + : `Periode ${index + 1}`, + icon: + (erOverstyrt || harÅpentUløstAksjonspunkt) && vilkarStatus === vilkårStatus.IKKE_VURDERT ? ( + + ) : null, + }))} + onClick={setActiveTab} + theme="arrow" + heading="Perioder" + /> +
+ {activePeriode && ( +
+ + +
+ )} +
+ ); +}; + +export default SoknadsfristVilkarProsessIndex; diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/FormState.ts b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/FormState.ts new file mode 100644 index 0000000000..c0e4299ac4 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/FormState.ts @@ -0,0 +1,11 @@ +interface AvklarteKrav { + erVilkarOk: string | boolean | null; + begrunnelse?: string; + journalpostId: string; + fraDato: string; +} + +export interface FormState { + isOverstyrt: boolean; + avklarteKrav: AvklarteKrav[]; +} diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/OverstyrBekreftKnappPanel.tsx b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/OverstyrBekreftKnappPanel.tsx new file mode 100644 index 0000000000..00984c4c91 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/OverstyrBekreftKnappPanel.tsx @@ -0,0 +1,21 @@ +import { Button } from '@navikt/ds-react'; + +interface OwnProps { + disabled?: boolean; + submitting: boolean; + pristine: boolean; + overrideReadOnly: boolean; +} + +const OverstyrBekreftKnappPanel = ({ disabled = false, submitting, pristine, overrideReadOnly }: OwnProps) => { + if (overrideReadOnly) { + return null; + } + return ( + + ); +}; + +export default OverstyrBekreftKnappPanel; diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.module.css b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.module.css new file mode 100644 index 0000000000..c3c57d1029 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.module.css @@ -0,0 +1,20 @@ +.container { + padding-right: 15px; +} + +.image { + height: 16px; + margin-top: -2px; + width: 16px; +} + +.fullBreddeIE { + width: 100%; +} + +.editButton { + :global(.navds-label) { + font-size: 0.875rem; + text-decoration: underline; + } +} diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.module.d.css.ts b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.module.d.css.ts new file mode 100644 index 0000000000..e5f7538af9 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.module.d.css.ts @@ -0,0 +1,8 @@ +declare const styles: { + readonly "container": string; + readonly "editButton": string; + readonly "fullBreddeIe": string; + readonly "image": string; +}; +export = styles; + diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.tsx b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.tsx new file mode 100644 index 0000000000..4ca66a573d --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarDokument.tsx @@ -0,0 +1,192 @@ +import { initializeDate } from '@k9-sak-web/lib/dateUtils/initializeDate.js'; +import { CheckmarkIcon, XMarkOctagonIcon } from '@navikt/aksel-icons'; +import { BodyShort, Button } from '@navikt/ds-react'; +import { Datepicker, TextAreaField } from '@navikt/ft-form-hooks'; +import { + dateAfterOrEqual, + dateBeforeOrEqual, + hasValidDate, + hasValidText, + maxLength, + minLength, + required, +} from '@navikt/ft-form-validators'; +import { AssessedBy } from '@navikt/ft-plattform-komponenter'; +import type { VilkårPeriodeDto } from '@navikt/k9-sak-typescript-client'; +import { useCallback, useMemo } from 'react'; +import RadioGroupPanel from '../../../shared/hook-form/RadioGroupPanel'; +import type { KravDokument } from '../types/KravDokumentStatus'; +import { formatDate } from '../utils'; +import styles from './SoknadsfristVilkarDokument.module.css'; + +const minLength3 = minLength(3); +const maxLength1500 = maxLength(1500); +interface SoknadsfristVilkarDokumentProps { + readOnly: boolean; + skalViseBegrunnelse?: boolean; + dokument: KravDokument; + dokumentIndex: number; + erAktivtDokument: boolean; + toggleEditForm: (shouldEdit: boolean) => void; + erOverstyrt?: boolean; + redigerVurdering?: boolean; + dokumentErVurdert: boolean; + periode: VilkårPeriodeDto; + kanEndrePåSøknadsopplysninger: boolean; +} + +export const DELVIS_OPPFYLT = 'DELVIS_OPPFYLT'; + +/** + * SoknadsfristVilkarDokument + * + * Presentasjonskomponent. Viser resultat av søknadsfristvilkåret når det ikke finnes tilknyttede aksjonspunkter. + * Resultatet kan overstyres av Nav-ansatt med overstyr-rettighet. + */ +export const SoknadsfristVilkarDokument = ({ + readOnly, + skalViseBegrunnelse = true, + dokument, + erAktivtDokument, + dokumentIndex, + toggleEditForm, + erOverstyrt, + redigerVurdering, + dokumentErVurdert, + periode, + kanEndrePåSøknadsopplysninger, +}: SoknadsfristVilkarDokumentProps) => { + const erVilkarOk = readOnly && dokumentErVurdert && periode.vilkarStatus === 'OPPFYLT'; + const opprettetTidspunkt = dokument?.avklarteOpplysninger?.opprettetTidspunkt; + const minDate = useMemo( + () => + dokument.status?.reduce( + (acc, curr) => (!acc || initializeDate(curr.periode.fom) < initializeDate(acc) ? curr.periode.fom : acc), + '', + ), + [dokument.journalpostId], + ); + const maxDate = useMemo( + () => + dokument.status?.reduce( + (acc, curr) => (!acc || initializeDate(curr.periode.tom) > initializeDate(acc) ? curr.periode.tom : acc), + '', + ), + [dokument.innsendingstidspunkt, dokument.journalpostId], + ); + + const isAtleastDate = useCallback(v => dateAfterOrEqual(minDate)(v), [minDate]); + const isAtmostDate = useCallback(v => dateBeforeOrEqual(maxDate)(v), [maxDate]); + const showRedigerVurderingButton = + !erOverstyrt && dokumentErVurdert && !redigerVurdering && kanEndrePåSøknadsopplysninger; + + return ( +
+

+ {dokument.type} innsendt {formatDate(dokument.innsendingstidspunkt)}{' '} + (journalpostId: {dokument.journalpostId}) +

+ {skalViseBegrunnelse && ( + <> +
+
+
+ + +
+
+ + )} +
+ {readOnly && erVilkarOk !== undefined && ( + <> +
+
+ {erVilkarOk ? ( + + ) : ( + + )} +
+
+ {erVilkarOk && Vilkåret er oppfylt for hele perioden} + {!erVilkarOk && ( + + Vilkåret er ikke oppfylt for denne perioden + + )} +
+
+
+ + )} + {(!readOnly || erVilkarOk === undefined) && ( + + +
+ ), + }, + { + value: 'false', + label: ( + <> + Vilkåret er ikke oppfylt for denne perioden + + ), + }, + ]} + /> + )} + {showRedigerVurderingButton && ( +
+
+ +
+ )} +
+
+ ); +}; + +export default SoknadsfristVilkarDokument; diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.module.css b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.module.css new file mode 100644 index 0000000000..4d5075df32 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.module.css @@ -0,0 +1,27 @@ +.status { + height: 20px; + width: 20px; +} + +.key { + cursor: pointer; + height: 20px; + width: 20px; +} + +.keyWithoutCursor { + height: 20px; + width: 20px; +} + +.aksjonspunkt { + padding: 0 20px 20px; + margin-left: -20px; + min-width: 400px; +} + +.apentAksjonspunkt { + padding-top: 20px; + border: 3px solid #fa3; + border-radius: 4px; +} diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.module.d.css.ts b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.module.d.css.ts new file mode 100644 index 0000000000..dc065ef0c1 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.module.d.css.ts @@ -0,0 +1,9 @@ +declare const styles: { + readonly "aksjonspunkt": string; + readonly "apentAksjonspunkt": string; + readonly "key": string; + readonly "keyWithoutCursor": string; + readonly "status": string; +}; +export = styles; + diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.tsx b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.tsx new file mode 100644 index 0000000000..a754ce53a5 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarForm.tsx @@ -0,0 +1,326 @@ +import { aksjonspunktkodeDefinisjonType } from '@k9-sak-web/backend/k9sak/kodeverk/AksjonspunktkodeDefinisjon.js'; +import { aksjonspunktStatus } from '@k9-sak-web/backend/k9sak/kodeverk/AksjonspunktStatus.js'; +import { aksjonspunktType } from '@k9-sak-web/backend/k9sak/kodeverk/AksjonspunktType.js'; +import { vilkårStatus } from '@k9-sak-web/backend/k9sak/kodeverk/behandling/VilkårStatus.js'; +import { initializeDate } from '@k9-sak-web/lib/dateUtils/initializeDate.js'; +import { ExclamationmarkTriangleFillIcon } from '@navikt/aksel-icons'; +import { Alert, Button, HStack, Label } from '@navikt/ds-react'; +import { Form } from '@navikt/ft-form-hooks'; +import { decodeHtmlEntity } from '@navikt/ft-utils'; +import type { Periode, VilkårPeriodeDto } from '@navikt/k9-sak-typescript-client'; +import { Dayjs } from 'dayjs'; +import hash from 'object-hash'; +import { useState, type SetStateAction } from 'react'; +import { useForm } from 'react-hook-form'; + +import type { KravDokument } from '../types/KravDokumentStatus'; +import type { SoknadsfristAksjonspunktType } from '../types/SoknadsfristAksjonspunktType'; +import type { SubmitData } from '../types/submitCallback'; +import { utledInnsendtSoknadsfrist } from '../utils'; +import type { FormState } from './FormState'; +import OverstyrBekreftKnappPanel from './OverstyrBekreftKnappPanel'; +import SoknadsfristVilkarDokument, { DELVIS_OPPFYLT } from './SoknadsfristVilkarDokument'; +import styles from './SoknadsfristVilkarForm.module.css'; + +/** + * Temporær fiks for saksbehandlere som setter dato og forventer at + * backend skal telle fra og meg datoen de setter. + * + * Backend teller fra dagen etter.. + */ +const minusEnDag = (dato: string | Dayjs) => initializeDate(dato).subtract(1, 'days').format('YYYY-MM-DD'); +const plusEnDag = (dato: string | Dayjs) => initializeDate(dato).add(1, 'days').format('YYYY-MM-DD'); + +const buildInitialValues = ( + aksjonspunkter: SoknadsfristAksjonspunktType[], + alleDokumenter: KravDokument[], + status: string, +): FormState => { + const overstyrtAksjonspunkt = aksjonspunkter.find( + ap => ap.definisjon === aksjonspunktkodeDefinisjonType.OVERSTYR_SOKNADSFRISTVILKAR, + ); + + return { + isOverstyrt: overstyrtAksjonspunkt !== undefined, + avklarteKrav: alleDokumenter.map(dokument => { + const fraDato = dokument.overstyrteOpplysninger?.fraDato || dokument.avklarteOpplysninger?.fraDato; + const innsendtSoknadsfrist = dokument.innsendingstidspunkt + ? utledInnsendtSoknadsfrist(dokument.innsendingstidspunkt) + : ''; + + const erAvklartEllerOverstyrt = !!fraDato; + + const erDelvisOppfylt = status !== vilkårStatus.OPPFYLT && fraDato && plusEnDag(fraDato) !== innsendtSoknadsfrist; + const erVilkarOk = erDelvisOppfylt ? DELVIS_OPPFYLT : status === vilkårStatus.OPPFYLT ? 'true' : 'false'; + return { + erVilkarOk: erAvklartEllerOverstyrt ? erVilkarOk : null, + begrunnelse: decodeHtmlEntity( + dokument.overstyrteOpplysninger?.begrunnelse || dokument.avklarteOpplysninger?.begrunnelse || '', + ), + journalpostId: dokument.journalpostId, + fraDato: fraDato ? plusEnDag(fraDato) : '', + }; + }), + }; +}; + +const transformValues = ( + values: FormState, + alleDokumenter: KravDokument[], + apKode: string, + periodeFom: string, + periodeTom: string, +): { + kode: string; + begrunnelse: string; + avklarteKrav: FormState['avklarteKrav']; + erVilkarOk: boolean; + periode?: Periode; +} => ({ + kode: apKode, + begrunnelse: values.avklarteKrav.map(krav => krav.begrunnelse).join('\n'), + avklarteKrav: values.avklarteKrav.map(krav => { + const dokumentStatus = alleDokumenter.find(d => d.journalpostId === krav.journalpostId); + const erVilkarOk = `${krav.erVilkarOk}` === 'true' || krav.erVilkarOk === DELVIS_OPPFYLT; + + const fraDato = (() => { + switch (krav.erVilkarOk) { + case 'true': + return dokumentStatus?.status?.reduce( + (acc, curr) => + !acc || (curr.periode.fom && initializeDate(curr.periode.fom).isBefore(initializeDate(acc))) + ? curr.periode.fom + : acc, + dokumentStatus.status?.[0]?.periode.fom, + ); + + case DELVIS_OPPFYLT: + return krav.fraDato; + + default: + return dokumentStatus?.innsendingstidspunkt + ? utledInnsendtSoknadsfrist(dokumentStatus.innsendingstidspunkt)?.toString() + : ''; + } + })(); + return { + ...krav, + erVilkarOk, + godkjent: erVilkarOk, + // fjern 'minusEnDag' hvis backend oppdateres.. + fraDato: fraDato ? minusEnDag(fraDato) : '', + }; + }), + erVilkarOk: !values.avklarteKrav.some(krav => !krav.erVilkarOk || krav.erVilkarOk === 'false'), + periode: periodeFom && periodeTom ? { fom: periodeFom, tom: periodeTom } : undefined, +}); + +interface SoknadsfristVilkarFormProps { + aksjonspunkter: SoknadsfristAksjonspunktType[]; + submitCallback: (props: SubmitData[]) => void; + periode: VilkårPeriodeDto; + erOverstyrt?: boolean; + harÅpentAksjonspunkt: boolean; + overrideReadOnly: boolean; + status: string; + toggleOverstyring: (overstyrtPanel: SetStateAction) => void; + alleDokumenter: KravDokument[]; + dokumenterIAktivPeriode?: KravDokument[]; + kanEndrePåSøknadsopplysninger: boolean; +} + +/** + * VilkarresultatForm + * + * Presentasjonskomponent. Viser resultat av vilkårskjøring når det ikke finnes tilknyttede aksjonspunkter. + * Resultatet kan overstyres av Nav-ansatt med overstyr-rettighet. + */ +export const SoknadsfristVilkarForm = ({ + erOverstyrt, + harÅpentAksjonspunkt, + overrideReadOnly, + toggleOverstyring, + alleDokumenter, + dokumenterIAktivPeriode, + aksjonspunkter, + periode, + status, + submitCallback, + kanEndrePåSøknadsopplysninger, +}: SoknadsfristVilkarFormProps) => { + const formMethods = useForm({ + defaultValues: buildInitialValues(aksjonspunkter, alleDokumenter, status), + }); + const [editForm, setEditForm] = useState(false); + + const toggleEditForm = (shouldEdit: boolean) => { + setEditForm(shouldEdit); + if (!shouldEdit) { + formMethods.reset(buildInitialValues(aksjonspunkter, alleDokumenter, status)); + } + }; + const aksjonspunkt = erOverstyrt + ? aksjonspunkter.find(ap => ap.definisjon === aksjonspunktkodeDefinisjonType.OVERSTYR_SOKNADSFRISTVILKAR) + : aksjonspunkter.find( + ap => ap.definisjon === aksjonspunktkodeDefinisjonType.KONTROLLER_OPPLYSNINGER_OM_SØKNADSFRIST, + ); + + const harAksjonspunkt = aksjonspunkt !== undefined; + const periodeFom = periode?.periode?.fom ?? ''; + const periodeTom = periode?.periode?.tom ?? ''; + const aksjonspunktCode = + erOverstyrt || !harAksjonspunkt + ? aksjonspunktkodeDefinisjonType.OVERSTYR_SOKNADSFRISTVILKAR + : aksjonspunktkodeDefinisjonType.KONTROLLER_OPPLYSNINGER_OM_SØKNADSFRIST; + + const harLøstManueltAksjonspunkt = aksjonspunkter.some( + ap => + ap.definisjon === aksjonspunktkodeDefinisjonType.KONTROLLER_OPPLYSNINGER_OM_SØKNADSFRIST && + ap.aksjonspunktType === aksjonspunktType.MANUELL && + ap.status === aksjonspunktStatus.UTFORT, + ); + + const isSolvable = + erOverstyrt || + (harÅpentAksjonspunkt || harLøstManueltAksjonspunkt || harAksjonspunkt + ? !(aksjonspunkt?.status && aksjonspunkt.status === aksjonspunktStatus.OPPRETTET && !aksjonspunkt.kanLoses) + : false); + + const isReadOnly = overrideReadOnly || !periode?.vurderesIBehandlingen; + + const toggleAv = () => { + formMethods.reset(); + toggleOverstyring(oldArray => + oldArray.filter(code => code !== aksjonspunktkodeDefinisjonType.OVERSTYR_SOKNADSFRISTVILKAR), + ); + }; + + const handleSubmit = (values: FormState) => { + submitCallback([transformValues(values, alleDokumenter, aksjonspunktCode, periodeFom, periodeTom)]); + }; + + const AksjonspunktText = () => { + if (harLøstManueltAksjonspunkt && !editForm) { + return ( + + ); + } + if (!isReadOnly) { + if (harÅpentAksjonspunkt || editForm) { + return ( +
+ + Vurder om søknadsfristvilkåret er oppfylt + +
+ ); + } + if (erOverstyrt) { + return ( + + ); + } + } + return <>; + }; + + return ( +
+ {Array.isArray(dokumenterIAktivPeriode) && dokumenterIAktivPeriode.length > 0 && ( +
+ {(erOverstyrt || harÅpentAksjonspunkt || editForm) && } + {Array.isArray(alleDokumenter) && alleDokumenter.length > 0 + ? alleDokumenter.map((dokument, index) => { + const documentHash = hash(dokument); + return ( + hash(d) === documentHash) > -1} + skalViseBegrunnelse={erOverstyrt || harAksjonspunkt || editForm} + readOnly={(isReadOnly || (!erOverstyrt && !harÅpentAksjonspunkt)) && !editForm} + dokumentIndex={index} + dokument={dokument} + toggleEditForm={toggleEditForm} + erOverstyrt={erOverstyrt} + redigerVurdering={editForm} + dokumentErVurdert={status !== vilkårStatus.IKKE_VURDERT} + periode={periode} + kanEndrePåSøknadsopplysninger={kanEndrePåSøknadsopplysninger} + /> + ); + }) + : 'Det finnes ingen dokumenter knyttet til denne behandlingen'} +
+ + {erOverstyrt && ( + <> +
+ + +
+
+ + + + + + )} + {(harÅpentAksjonspunkt || editForm) && !erOverstyrt && ( + + + {editForm && ( + + )} + + )} +
+ )} + + ); +}; + +export default SoknadsfristVilkarForm; diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarHeader.tsx b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarHeader.tsx new file mode 100644 index 0000000000..f75a35a9fc --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/components/SoknadsfristVilkarHeader.tsx @@ -0,0 +1,118 @@ +import { vilkårStatus } from '@k9-sak-web/backend/k9sak/kodeverk/behandling/VilkårStatus.js'; +import { Lovreferanse } from '@k9-sak-web/gui/shared/lovreferanse/Lovreferanse.js'; +import { CheckmarkCircleFillIcon, KeyHorizontalIcon, XMarkOctagonFillIcon } from '@navikt/aksel-icons'; +import { Button, Detail, Heading, HStack, Label } from '@navikt/ds-react'; +import type { SetStateAction } from 'react'; +import type { SoknadsfristAksjonspunktType } from '../types/SoknadsfristAksjonspunktType'; + +const isOverridden = (aksjonspunktCode: string, aksjonspunktCodes?: string[]) => + aksjonspunktCodes?.some(code => code === aksjonspunktCode); +const isHidden = (kanOverstyre: boolean, aksjonspunktCode: string, aksjonspunktCodes?: string[]) => + !isOverridden(aksjonspunktCode, aksjonspunktCodes) && !kanOverstyre; + +const VilkarOkMessage = ({ originalErVilkarOk }: { originalErVilkarOk: boolean }) => { + let message = 'Ikke behandlet'; + if (originalErVilkarOk) { + message = 'Vilkåret er oppfylt for hele perioden'; + } else if (originalErVilkarOk === false) { + message = 'Vilkåret er avslått'; + } + + return ( + + ); +}; + +interface SoknadsfristVilkarHeaderProps { + aksjonspunkter: SoknadsfristAksjonspunktType[]; + erOverstyrt?: boolean; + kanOverstyreAccess?: { + employeeHasAccess: boolean; + isEnabled: boolean; + }; + lovReferanse?: string; + overrideReadOnly: boolean; + overstyringApKode: string; + panelTittelKode: string; + toggleOverstyring: (overstyrtPanel: SetStateAction) => void; + status: string; +} + +const SoknadsfristVilkarHeader = ({ + panelTittelKode, + erOverstyrt, + overstyringApKode, + lovReferanse, + overrideReadOnly, + kanOverstyreAccess, + aksjonspunkter, + status, + toggleOverstyring, +}: SoknadsfristVilkarHeaderProps) => { + const aksjonspunktCodes = aksjonspunkter.map(a => a.definisjon).filter(a => !!a); + const erOppfylt = vilkårStatus.OPPFYLT === status; + const originalErVilkarOk = vilkårStatus.IKKE_VURDERT !== status ? erOppfylt : undefined; + const togglePa = () => { + toggleOverstyring(oldArray => [...oldArray, overstyringApKode]); + }; + return ( + <> + <> + + {!erOverstyrt && originalErVilkarOk !== undefined && ( + <> + {originalErVilkarOk ? ( + + ) : ( + + )} + + )} + + {panelTittelKode} + + {lovReferanse && ( + + {lovReferanse} + + )} + +
+
+
+ +
+ {originalErVilkarOk !== undefined && + kanOverstyreAccess?.employeeHasAccess && + !isHidden(kanOverstyreAccess.isEnabled, overstyringApKode, aksjonspunktCodes) && ( + <> + {!erOverstyrt && !overrideReadOnly && ( +
+
+ )} + {(erOverstyrt || overrideReadOnly) && ( +
+
+ +
+ )} + + )} +
+ +
+ + ); +}; + +export default SoknadsfristVilkarHeader; diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/KravDokumentStatus.ts b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/KravDokumentStatus.ts new file mode 100644 index 0000000000..105cf1943f --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/KravDokumentStatus.ts @@ -0,0 +1,20 @@ +import type { KravDokumentStatusType } from '@k9-sak-web/backend/k9sak/kodeverk/KravDokumentStatus.js'; +import type { SøknadsfristPeriode } from './SøknadsfristPeriode'; + +type AvklarteOpplysninger = { + begrunnelse?: string; + fraDato: string; + godkjent: boolean; + opprettetAv?: string; + opprettetTidspunkt?: string; +}; + +export type KravDokument = { + avklarteOpplysninger?: AvklarteOpplysninger; + innsendingstidspunkt: string; + journalpostId: string; + overstyrteOpplysninger?: AvklarteOpplysninger; + status: Array; + type?: KravDokumentStatusType; + id?: string; +}; diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/SoknadsfristAksjonspunktType.ts b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/SoknadsfristAksjonspunktType.ts new file mode 100644 index 0000000000..c84fdcc815 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/SoknadsfristAksjonspunktType.ts @@ -0,0 +1,10 @@ +import type { AksjonspunktStatus } from '@k9-sak-web/backend/k9sak/kodeverk/AksjonspunktStatus.js'; +import type { AksjonspunktType } from '@k9-sak-web/backend/k9sak/kodeverk/AksjonspunktType.js'; + +export type SoknadsfristAksjonspunktType = { + aksjonspunktType: AksjonspunktType; + begrunnelse?: string; + definisjon: string; + status: AksjonspunktStatus; + kanLoses: boolean; +}; diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/SoknadsfristVilkarType.ts b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/SoknadsfristVilkarType.ts new file mode 100644 index 0000000000..bd1d8b7b26 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/SoknadsfristVilkarType.ts @@ -0,0 +1,3 @@ +import type { VilkårPeriodeDto } from '@k9-sak-web/backend/k9sak/generated'; + +export type SoknadsfristVilkarType = { lovReferanse?: string; perioder?: Array }; diff --git "a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/S\303\270knadsfristPeriode.ts" "b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/S\303\270knadsfristPeriode.ts" new file mode 100644 index 0000000000..7c57cb5278 --- /dev/null +++ "b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/S\303\270knadsfristPeriode.ts" @@ -0,0 +1,9 @@ +import type { VilkårStatus } from '@k9-sak-web/backend/k9sak/kodeverk/behandling/VilkårStatus.js'; + +export type SøknadsfristPeriode = { + periode: { + fom: string; + tom: string; + }; + status?: VilkårStatus; +}; diff --git "a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/S\303\270knadsfristTilstand.ts" "b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/S\303\270knadsfristTilstand.ts" new file mode 100644 index 0000000000..9b78228e0e --- /dev/null +++ "b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/S\303\270knadsfristTilstand.ts" @@ -0,0 +1,5 @@ +import type { KravDokument } from './KravDokumentStatus'; + +export type SøknadsfristTilstand = { + dokumentStatus: Array; +}; diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/submitCallback.ts b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/submitCallback.ts new file mode 100644 index 0000000000..0f814a66ad --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/types/submitCallback.ts @@ -0,0 +1,4 @@ +export type SubmitData = Readonly<{ + kode: string; + begrunnelse?: string; +}>; diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/utils.spec.ts b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/utils.spec.ts new file mode 100644 index 0000000000..049a834cfc --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/utils.spec.ts @@ -0,0 +1,130 @@ +import { vilkårStatus } from '@k9-sak-web/backend/k9sak/kodeverk/behandling/VilkårStatus.js'; +import { initializeDate } from '@k9-sak-web/lib/dateUtils/initializeDate.js'; +import { formatDate, hentAktivePerioderFraVilkar, utledInnsendtSoknadsfrist } from './utils'; + +describe('formatDate', () => { + it('should format a valid date string correctly', () => { + const result = formatDate('2023-01-01'); + expect(result).toBe('01.01.2023'); + }); + + it('should handle an invalid date string', () => { + const result = formatDate('invalid-date'); + expect(result).toBe('Invalid Date'); + }); + + it('should handle an empty date string', () => { + const result = formatDate(''); + expect(result).toBe('Invalid Date'); + }); +}); + +describe('utledInnsendtSoknadsfrist', () => { + it('should return formatted date string for a valid date input', () => { + const result = utledInnsendtSoknadsfrist('2023-01-01'); + expect(result).toBe('2022-10-01'); + }); + + it('should return date object for a valid date input without formatting', () => { + const result = utledInnsendtSoknadsfrist('2023-01-01', false); + expect(result).toStrictEqual(initializeDate('2022-10-01')); + }); + + it('should handle an invalid date string', () => { + const result = utledInnsendtSoknadsfrist('invalid-date'); + expect(result).toBe('Invalid Date'); + }); +}); + +describe('hentAktivePerioderFraVilkar', () => { + it('should return an empty array if there are no periods in vilkar', () => { + const vilkar = [{ perioder: [] }]; + const result = hentAktivePerioderFraVilkar(vilkar, true); + expect(result).toEqual([]); + }); + + it('should return filtered and sorted periods when visAllePerioder is true', () => { + const vilkar = [ + { + perioder: [ + { + periode: { fom: '2023-01-01', tom: '' }, + vurderesIBehandlingen: false, + vilkarStatus: vilkårStatus.IKKE_VURDERT, + }, + { + periode: { fom: '2023-02-01', tom: '' }, + vurderesIBehandlingen: true, + vilkarStatus: vilkårStatus.IKKE_VURDERT, + }, + ], + }, + ]; + + const result = hentAktivePerioderFraVilkar(vilkar, true); + expect(result).toEqual([ + { + periode: { fom: '2023-01-01', tom: '' }, + vurderesIBehandlingen: false, + vilkarStatus: vilkårStatus.IKKE_VURDERT, + }, + ]); + }); + + it('should return filtered and sorted periods when visAllePerioder is false', () => { + const vilkar = [ + { + perioder: [ + { + periode: { fom: '2023-01-01', tom: '' }, + vurderesIBehandlingen: false, + vilkarStatus: vilkårStatus.IKKE_VURDERT, + }, + { + periode: { fom: '2023-02-01', tom: '' }, + vurderesIBehandlingen: true, + vilkarStatus: vilkårStatus.IKKE_VURDERT, + }, + ], + }, + ]; + + const result = hentAktivePerioderFraVilkar(vilkar, false); + expect(result).toEqual([ + { periode: { fom: '2023-02-01', tom: '' }, vurderesIBehandlingen: true, vilkarStatus: vilkårStatus.IKKE_VURDERT }, + ]); + }); + + it('should return sorted periods in reverse order', () => { + const vilkar = [ + { + perioder: [ + { + periode: { fom: '2023-01-01', tom: '' }, + vurderesIBehandlingen: false, + vilkarStatus: vilkårStatus.IKKE_VURDERT, + }, + { + periode: { fom: '2023-02-01', tom: '' }, + vurderesIBehandlingen: false, + vilkarStatus: vilkårStatus.IKKE_VURDERT, + }, + ], + }, + ]; + + const result = hentAktivePerioderFraVilkar(vilkar, true); + expect(result).toEqual([ + { + periode: { fom: '2023-02-01', tom: '' }, + vurderesIBehandlingen: false, + vilkarStatus: vilkårStatus.IKKE_VURDERT, + }, + { + periode: { fom: '2023-01-01', tom: '' }, + vurderesIBehandlingen: false, + vilkarStatus: vilkårStatus.IKKE_VURDERT, + }, + ]); + }); +}); diff --git a/packages/v2/gui/src/prosess/vilkar-soknadsfrist/utils.ts b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/utils.ts new file mode 100644 index 0000000000..9d21bb1ec7 --- /dev/null +++ b/packages/v2/gui/src/prosess/vilkar-soknadsfrist/utils.ts @@ -0,0 +1,48 @@ +import { DDMMYYYY_DATE_FORMAT } from '@k9-sak-web/lib/dateUtils/formats.js'; +import { initializeDate } from '@k9-sak-web/lib/dateUtils/initializeDate.js'; +import { Dayjs } from 'dayjs'; +import type { SoknadsfristVilkarType } from './types/SoknadsfristVilkarType'; + +export const formatDate = (dato: string) => initializeDate(dato).format(DDMMYYYY_DATE_FORMAT); + +export const utledInnsendtSoknadsfrist = (innsendtDato: string, formatDate: boolean = true) => { + const dt = initializeDate(innsendtDato).startOf('month').subtract(3, 'months'); + + if (formatDate) { + return dt.format('YYYY-MM-DD'); + } + + return dt; +}; + +export const dateSorter = (date1: Dayjs, date2: Dayjs) => { + if (date1.isBefore(date2)) { + return -1; + } + if (date2.isBefore(date1)) { + return 1; + } + return 0; +}; + +export const dateStringSorter = (date1: string, date2: string) => { + const date1AsDayjs = initializeDate(date1); + const date2AsDayjs = initializeDate(date2); + return dateSorter(date1AsDayjs, date2AsDayjs); +}; + +export const hentAktivePerioderFraVilkar = (vilkar: SoknadsfristVilkarType[], visAllePerioder: boolean) => { + const [activeVilkår] = vilkar; + + if (!activeVilkår?.perioder) { + return []; + } + + return activeVilkår.perioder + .filter( + periode => + (visAllePerioder && !periode.vurderesIBehandlingen) || (periode.vurderesIBehandlingen && !visAllePerioder), + ) + .sort((a, b) => (a.periode.fom && b.periode.fom ? dateStringSorter(a.periode.fom, b.periode.fom) : 0)) + .reverse(); +}; diff --git a/packages/v2/gui/src/shared/hook-form/RadioGroupPanel.tsx b/packages/v2/gui/src/shared/hook-form/RadioGroupPanel.tsx new file mode 100644 index 0000000000..9541800905 --- /dev/null +++ b/packages/v2/gui/src/shared/hook-form/RadioGroupPanel.tsx @@ -0,0 +1,80 @@ +import { ErrorMessage } from '@hookform/error-message'; +import { Radio, RadioGroup } from '@navikt/ds-react'; +import React, { type ReactElement } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { getError } from './formUtils'; + +interface RadioProps { + value: string; + label: React.ReactNode; + id?: string; + element?: ReactElement; +} + +interface RadioGroupPanelProps { + question: React.ReactNode; + name: string; + radios: RadioProps[]; + validators?: Record any>; + onChange?: (value: string) => void; + disabled?: boolean; + readOnly?: boolean; +} + +const RadioGroupPanel = ({ + question, + name, + validators, + radios, + onChange, + disabled, + readOnly, +}: RadioGroupPanelProps) => { + const { control, formState } = useFormContext(); + const { errors } = formState; + const customOnChange = onChange; + return ( + { + const reactHookFormOnChange = field.onChange; + const valueToString = `${field.value}`; + return ( + } + size="small" + readOnly={readOnly} + value={valueToString} + > + {radios.map(radio => ( + + { + if (customOnChange) { + customOnChange(radio.value); + } + reactHookFormOnChange(radio.value); + }} + disabled={disabled} + value={radio.value} + > + {radio.label} + + {radio.value === field.value && radio.element} + + ))} + + ); + }} + /> + ); +}; + +export default RadioGroupPanel; diff --git a/packages/v2/gui/src/shared/hook-form/formUtils.ts b/packages/v2/gui/src/shared/hook-form/formUtils.ts new file mode 100644 index 0000000000..f9eb7be73a --- /dev/null +++ b/packages/v2/gui/src/shared/hook-form/formUtils.ts @@ -0,0 +1,4 @@ +export const getError = (errors: { [x: string]: any }, name: string): string | undefined => { + const error = name.split('.').reduce((o, i) => (o !== undefined ? o[i] : o), errors); + return error?.['message']; +}; diff --git a/yarn.lock b/yarn.lock index 62307e2788..031c0e6fed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2469,13 +2469,14 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.0.0": - version: 2.0.0 - resolution: "@formatjs/ecma402-abstract@npm:2.0.0" +"@formatjs/ecma402-abstract@npm:2.2.0": + version: 2.2.0 + resolution: "@formatjs/ecma402-abstract@npm:2.2.0" dependencies: - "@formatjs/intl-localematcher": 0.5.4 - tslib: ^2.4.0 - checksum: 0bba3b4f1a966c72d3f53173d650294fe313825b6451396c1040fb92bb86b2f771729888a1dadbc0a0074ef809229033fe8ff17c86dcb07a8ad42253b0c3a269 + "@formatjs/fast-memoize": 2.2.1 + "@formatjs/intl-localematcher": 0.5.5 + tslib: ^2.7.0 + checksum: 1db1a15d0a0978dfbc68e2e92096384fdc2b218d0430715b6e82607231d3eb9248818066f797ed87d195b59ec1e068736bc40bf40b39d25dfc02510e3681b0e3 languageName: node linkType: hard @@ -2490,12 +2491,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/fast-memoize@npm:2.2.0": - version: 2.2.0 - resolution: "@formatjs/fast-memoize@npm:2.2.0" +"@formatjs/fast-memoize@npm:2.2.1": + version: 2.2.1 + resolution: "@formatjs/fast-memoize@npm:2.2.1" dependencies: - tslib: ^2.4.0 - checksum: 8697fe72a7ece252d600a7d08105f2a2f758e2dd96f54ac0a4c508b1205a559fc08835635e1f8e5ca9dcc3ee61ce1fca4a0e7047b402f29fc96051e293a280ff + tslib: ^2.7.0 + checksum: 7bb12904d4c93cfae17006388b26d97cc0e32fef5af510f80974437382cd13a88b439b4407d96b6646af1806977cf34113f3dc8f1c96b28f73856700ee7857fd languageName: node linkType: hard @@ -2508,14 +2509,14 @@ __metadata: languageName: node linkType: hard -"@formatjs/icu-messageformat-parser@npm:2.7.8": - version: 2.7.8 - resolution: "@formatjs/icu-messageformat-parser@npm:2.7.8" +"@formatjs/icu-messageformat-parser@npm:2.7.10": + version: 2.7.10 + resolution: "@formatjs/icu-messageformat-parser@npm:2.7.10" dependencies: - "@formatjs/ecma402-abstract": 2.0.0 - "@formatjs/icu-skeleton-parser": 1.8.2 - tslib: ^2.4.0 - checksum: 404d6732653632eae3b10cfa70dc57c4fb0fe500c6ef9e687e938e4cb29e18b4e5d46633c88a2c06864328eb2f4713fbb6be404c6033682370d568971e2dda0d + "@formatjs/ecma402-abstract": 2.2.0 + "@formatjs/icu-skeleton-parser": 1.8.4 + tslib: ^2.7.0 + checksum: 7764ebfeaee8b4b9971c4d8ef1f16ddd13bd9551b35daefdf858f0bf5dd635c9cce81554f8f23293069829ec5599e8a703fbe8c51741fbd3e9eb9132301a3b38 languageName: node linkType: hard @@ -2530,13 +2531,13 @@ __metadata: languageName: node linkType: hard -"@formatjs/icu-skeleton-parser@npm:1.8.2": - version: 1.8.2 - resolution: "@formatjs/icu-skeleton-parser@npm:1.8.2" +"@formatjs/icu-skeleton-parser@npm:1.8.4": + version: 1.8.4 + resolution: "@formatjs/icu-skeleton-parser@npm:1.8.4" dependencies: - "@formatjs/ecma402-abstract": 2.0.0 - tslib: ^2.4.0 - checksum: 8735322fa93ddd471822ba77400411660cb6221c87955cdcea159e8f9b72188106b4d4bf57d737d248810ae1974e1df4974914a6fb6045e91bf5ea22cc7fd30f + "@formatjs/ecma402-abstract": 2.2.0 + tslib: ^2.7.0 + checksum: cf788c6bea06f8e011e1edd5576a947697169447aa088dc9e3c67effcad1ef1f2c7dd6b2d2752990433b5042305a5409058c9efc4396ff1fd5a5dee5d39e20ec languageName: node linkType: hard @@ -2550,14 +2551,14 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-displaynames@npm:6.6.8": - version: 6.6.8 - resolution: "@formatjs/intl-displaynames@npm:6.6.8" +"@formatjs/intl-displaynames@npm:6.6.10": + version: 6.6.10 + resolution: "@formatjs/intl-displaynames@npm:6.6.10" dependencies: - "@formatjs/ecma402-abstract": 2.0.0 - "@formatjs/intl-localematcher": 0.5.4 - tslib: ^2.4.0 - checksum: c68bf238034ccac36be83baf27b9561cab4925018fd6a41782532d5d901ca2c8790d01c40dc8eaea7156dd6aa1f5a8942f62242597704e2c267f0296092b49fd + "@formatjs/ecma402-abstract": 2.2.0 + "@formatjs/intl-localematcher": 0.5.5 + tslib: ^2.7.0 + checksum: 3a0c44d06dc8dd6de66aa6c1148c7a0fc692e5dc3ebbdbf2d85be8f3d9f0b2c76c58e0e0d3bc2f5e423367ccf69d37c5e87bfaac74064f7d08c29755ae632de8 languageName: node linkType: hard @@ -2572,14 +2573,14 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-listformat@npm:7.5.7": - version: 7.5.7 - resolution: "@formatjs/intl-listformat@npm:7.5.7" +"@formatjs/intl-listformat@npm:7.5.9": + version: 7.5.9 + resolution: "@formatjs/intl-listformat@npm:7.5.9" dependencies: - "@formatjs/ecma402-abstract": 2.0.0 - "@formatjs/intl-localematcher": 0.5.4 - tslib: ^2.4.0 - checksum: f1f920442b553ec2d9d04ed081ee3fd650039cc55316e52cc7c2b76e03eb4c654006c4338d8ade4c6134eaa68e1d4945e0d3e473935547fd2e21d9c0ce9721a4 + "@formatjs/ecma402-abstract": 2.2.0 + "@formatjs/intl-localematcher": 0.5.5 + tslib: ^2.7.0 + checksum: b87eb4d361e23815e16dc3a7937cee3cf351e6f3e8e29aeef4ed124d009160cd02ebf668b11ad4eee35e65c573da363a5b91ffb6cac95547990be346855e153e languageName: node linkType: hard @@ -2594,12 +2595,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.5.4": - version: 0.5.4 - resolution: "@formatjs/intl-localematcher@npm:0.5.4" +"@formatjs/intl-localematcher@npm:0.5.5": + version: 0.5.5 + resolution: "@formatjs/intl-localematcher@npm:0.5.5" dependencies: - tslib: ^2.4.0 - checksum: a0af57874fcd163add5f7a0cb1c008e9b09feb1d24cbce1263379ae0393cddd6681197a7f2f512f351a97666fc8675ed52cc17d1834266ee8fc65e9edf3435f6 + tslib: ^2.7.0 + checksum: 3821b84e3565aef4dfcc32b128aba26bf8e0b9d80a73804857fee32064a644cc9f06a186248264a2e4a268dec4c72962f6e8a41154bc9da60ca81525e31b5051 languageName: node linkType: hard @@ -2632,23 +2633,23 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl@npm:2.10.4": - version: 2.10.4 - resolution: "@formatjs/intl@npm:2.10.4" +"@formatjs/intl@npm:2.10.8": + version: 2.10.8 + resolution: "@formatjs/intl@npm:2.10.8" dependencies: - "@formatjs/ecma402-abstract": 2.0.0 - "@formatjs/fast-memoize": 2.2.0 - "@formatjs/icu-messageformat-parser": 2.7.8 - "@formatjs/intl-displaynames": 6.6.8 - "@formatjs/intl-listformat": 7.5.7 - intl-messageformat: 10.5.14 - tslib: ^2.4.0 + "@formatjs/ecma402-abstract": 2.2.0 + "@formatjs/fast-memoize": 2.2.1 + "@formatjs/icu-messageformat-parser": 2.7.10 + "@formatjs/intl-displaynames": 6.6.10 + "@formatjs/intl-listformat": 7.5.9 + intl-messageformat: 10.7.0 + tslib: ^2.7.0 peerDependencies: typescript: ^4.7 || 5 peerDependenciesMeta: typescript: optional: true - checksum: 13a1fada258c528c1617c0a3b49af3d0a6c4ed610e8fc2bd9d6f75909556d9dc1442ec5cb016c81fe1f9482e41611bd7841485d04c1ddbec9f4b2310f80e7259 + checksum: 4a0dc14c0a1fe9ed95f7e07de6dbc892a4d264dfc1fab4d766b81126d5b067d4736dfd671eadcf65e8dc980cd722341eebd653e4678c85c2a549c0ec05feb6d0 languageName: node linkType: hard @@ -4635,12 +4636,15 @@ __metadata: version: 0.0.0-use.local resolution: "@k9-sak-web/gui@workspace:packages/v2/gui" dependencies: + "@hookform/error-message": ^2.0.1 "@k9-sak-web/backend": "workspace:^" "@k9-sak-web/lib": 1.0.0 - "@navikt/ft-form-validators": 2.4.1 - "@navikt/ft-utils": 2.4.0 + "@navikt/ft-form-validators": 2.4.5 + "@navikt/ft-utils": 2.4.5 "@tanstack/react-query": ^5.59.19 + "@types/object-hash": ^3.0.6 axios: 1.7.7 + object-hash: ^3.0.0 react: 18.3.1 languageName: unknown linkType: soft @@ -5305,16 +5309,16 @@ __metadata: languageName: node linkType: hard -"@navikt/ft-form-validators@npm:2.4.1": - version: 2.4.1 - resolution: "@navikt/ft-form-validators@npm:2.4.1::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40navikt%2Fft-form-validators%2F2.4.1%2F5ceb0774eaa023a7a2b23efa6c404432a4c0fee9" +"@navikt/ft-form-validators@npm:2.4.5": + version: 2.4.5 + resolution: "@navikt/ft-form-validators@npm:2.4.5::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40navikt%2Fft-form-validators%2F2.4.5%2F2662f8fc09cc8a0ae27618f1cc80e4e4fbba6a0f" dependencies: - "@navikt/ft-utils": ^2.4.1 + "@navikt/ft-utils": ^2.4.5 moment: 2.30.1 peerDependencies: "@navikt/ft-utils": 2.x moment: 2.30.1 - checksum: dbe94a40c0e6ecc19be3026a965441bed492378704d3e0ecae8d5242d83024e43070d236b12216ea3e19237bf462ca46cadbe6972d7a50b7a507b6b6352a6c66 + checksum: 0671a06624abe921d7c8ac1da2b07bdf67d4e0757a8094c15d174abc7541875ac7a4531aef712a0ae93e6bfd8e48fb57360c3143f84746ac4305965a268aa857 languageName: node linkType: hard @@ -5331,7 +5335,7 @@ __metadata: languageName: node linkType: hard -"@navikt/ft-kodeverk@npm:^2.5.0, @navikt/ft-kodeverk@npm:^2.5.6": +"@navikt/ft-kodeverk@npm:^2.5.4, @navikt/ft-kodeverk@npm:^2.5.6": version: 2.5.6 resolution: "@navikt/ft-kodeverk@npm:2.5.6::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40navikt%2Fft-kodeverk%2F2.5.6%2F30482afc3b26e7b1358a9117898d1c2e7897d8de" checksum: c17f6636458f7330470d50dbc99f094910d1addf205a2de6624d56a7c11e33b616c5679dbfc458b5d51b157d2b2c708207a1aedbdf3752223a614e31eaa6e252 @@ -5446,7 +5450,7 @@ __metadata: languageName: node linkType: hard -"@navikt/ft-types@npm:2.5.7, @navikt/ft-types@npm:^2.5.0, @navikt/ft-types@npm:^2.5.7": +"@navikt/ft-types@npm:2.5.7, @navikt/ft-types@npm:^2.5.5, @navikt/ft-types@npm:^2.5.7": version: 2.5.7 resolution: "@navikt/ft-types@npm:2.5.7::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40navikt%2Fft-types%2F2.5.7%2Fd3dca3f2e7b2b38fbd8b7b774cd382dcb0647782" dependencies: @@ -5484,24 +5488,25 @@ __metadata: languageName: node linkType: hard -"@navikt/ft-utils@npm:2.4.0": - version: 2.4.0 - resolution: "@navikt/ft-utils@npm:2.4.0::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40navikt%2Fft-utils%2F2.4.0%2F73d041fdc711d9f5f7e42c027fd7949537076bb4" +"@navikt/ft-utils@npm:2.4.5": + version: 2.4.5 + resolution: "@navikt/ft-utils@npm:2.4.5::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40navikt%2Fft-utils%2F2.4.5%2Fb938f10c0389a31660c4d17b6c440cb0f9f712bd" dependencies: - "@navikt/ft-kodeverk": ^2.5.0 - "@navikt/ft-types": ^2.5.0 + "@navikt/ft-kodeverk": ^2.5.4 + "@navikt/ft-types": ^2.5.5 dayjs: 1.11.13 - react-intl: 6.6.8 + react-intl: 6.8.0 peerDependencies: "@navikt/ft-kodeverk": 2.x "@navikt/ft-types": 2.x - dayjs: 1.11.11 - react-intl: 6.6.8 - checksum: a141e1a5cf6d9c848c4ea345899fc2138d3e14a52b6f98540c95a9d32225fc14c3b9118bf4c3644765222f126ec7010768c0898075624e3588059b6d2cd4f2ce + dayjs: 1.11.x + react: "*" + react-intl: 6.x + checksum: d8a14dba663baf6cfe95063fb9647ab79cdac7bb7257f09b3a9e9ef625f820a1cebe3d6686d261f5b95a8278f09f8012c6c4c9975f3b3bef5f332cf979a69a25 languageName: node linkType: hard -"@navikt/ft-utils@npm:^2.4.1, @navikt/ft-utils@npm:^2.4.8": +"@navikt/ft-utils@npm:^2.4.5, @navikt/ft-utils@npm:^2.4.8": version: 2.4.8 resolution: "@navikt/ft-utils@npm:2.4.8::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40navikt%2Fft-utils%2F2.4.8%2Ff62af50f42384f22c910903742cd3e0e2a49da7a" dependencies: @@ -7118,6 +7123,13 @@ __metadata: languageName: node linkType: hard +"@types/object-hash@npm:^3.0.6": + version: 3.0.6 + resolution: "@types/object-hash@npm:3.0.6" + checksum: 2c7979d4e540af817b99c09fb4c2fed1c0b0e14342df474d8dcde4165a82c440b038341fd66fe998d9f86acdd5cccc65ba8092315e39e7c2114f945fa70aaa56 + languageName: node + linkType: hard + "@types/prop-types@npm:*": version: 15.7.12 resolution: "@types/prop-types@npm:15.7.12" @@ -11817,15 +11829,15 @@ __metadata: languageName: node linkType: hard -"intl-messageformat@npm:10.5.14": - version: 10.5.14 - resolution: "intl-messageformat@npm:10.5.14" +"intl-messageformat@npm:10.7.0": + version: 10.7.0 + resolution: "intl-messageformat@npm:10.7.0" dependencies: - "@formatjs/ecma402-abstract": 2.0.0 - "@formatjs/fast-memoize": 2.2.0 - "@formatjs/icu-messageformat-parser": 2.7.8 - tslib: ^2.4.0 - checksum: 7aaed153283eb83720d72df7757390515a79a1823ea9f4191c69859f1e5dd0d9a7463e5f9186fe77a31414ed98fc81619fb4c838ffdf6d481b1b370403337ca3 + "@formatjs/ecma402-abstract": 2.2.0 + "@formatjs/fast-memoize": 2.2.1 + "@formatjs/icu-messageformat-parser": 2.7.10 + tslib: ^2.7.0 + checksum: 9feb3ded496dc62fcc205c19f5d2ae77a934aa1025dc33b20e577fc8e04679fe185fe94513ec6d336be339682b4f36afb7b378c379fc92f45908721b4d11d81b languageName: node linkType: hard @@ -13230,6 +13242,7 @@ __metadata: "@testing-library/react": 16.0.1 "@testing-library/user-event": 14.5.2 "@types/history": 5.0.0 + "@types/object-hash": ^3.0.6 "@types/prop-types": 15.7.13 "@types/react": 18.3.12 "@types/react-collapse": 5.0.4 @@ -15448,27 +15461,27 @@ __metadata: languageName: node linkType: hard -"react-intl@npm:6.6.8": - version: 6.6.8 - resolution: "react-intl@npm:6.6.8" +"react-intl@npm:6.8.0": + version: 6.8.0 + resolution: "react-intl@npm:6.8.0" dependencies: - "@formatjs/ecma402-abstract": 2.0.0 - "@formatjs/icu-messageformat-parser": 2.7.8 - "@formatjs/intl": 2.10.4 - "@formatjs/intl-displaynames": 6.6.8 - "@formatjs/intl-listformat": 7.5.7 + "@formatjs/ecma402-abstract": 2.2.0 + "@formatjs/icu-messageformat-parser": 2.7.10 + "@formatjs/intl": 2.10.8 + "@formatjs/intl-displaynames": 6.6.10 + "@formatjs/intl-listformat": 7.5.9 "@types/hoist-non-react-statics": ^3.3.1 - "@types/react": 16 || 17 || 18 + "@types/react": ^18.3.11 hoist-non-react-statics: ^3.3.2 - intl-messageformat: 10.5.14 - tslib: ^2.4.0 + intl-messageformat: 10.7.0 + tslib: ^2.7.0 peerDependencies: react: ^16.6.0 || 17 || 18 typescript: ^4.7 || 5 peerDependenciesMeta: typescript: optional: true - checksum: 41e43d1d15f33f4b5abb619645e96f1d81f64bba62745e19a289ea84c1580bf39fd5da6b94104c391ed689f1f1daec512dcb396cac3df58322bc060b9f5bdb01 + checksum: 109c643a5fcca471fd1ba3b7090c285fd5ad19edfb6c6114aa4cbe1891c184bbf4a64930e6ac1f8cd2e52eb9b87ae2368c49d059055765935094da865f7b4c83 languageName: node linkType: hard @@ -17366,7 +17379,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.4.0": +"tslib@npm:^2.7.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: e4aba30e632b8c8902b47587fd13345e2827fa639e7c3121074d5ee0880723282411a8838f830b55100cbe4517672f84a2472667d355b81e8af165a55dc6203a