Skip to content

Commit

Permalink
Ung brev (#6945)
Browse files Browse the repository at this point in the history
* Påbegynt brev for ung-web

* Kjører window.open på respons fra forhåndsvis

* bruker axios for pushing midlertidig

* åpner pdf i ny fane

* Legger inn checkbokser for håndtering av brev
Legger inn submit-knapp for vedtaksteget

* Viser avslagsårsaker

* Storybook-story
Lager custom dto som bare inneholder det vi faktisk trenger i komponenten

* read only på checkbokser

* Fikser slik at kall for brev forhåndsvisning fungerer med generert klient. (#6959)

Generert klientkode var for gammal.

* Forhåndsvis skal ikke submitte

* Fiks accept header på kall til /formidling/vedtaksbrev/forhaandsvis.

Dette kallet skal ha Accept: application/pdf for å fungere, istadenfor standard application/json.

Denen fiks er ein midlertidig workaround inntil generert klient støtter å sette korrekt accept header på ein betre måte.

* Returnerer fra query for å forhindre feilmelding
Query data cannot be undefined. Please make sure to return a value other than undefined from your query function.

* Sjekker om det finnes tilgjengelige vedtaksbrev

* Fiks story

---------

Co-authored-by: Hallvard Andreas Stark <[email protected]>
Co-authored-by: Jostein Stuhaug <[email protected]>
Co-authored-by: Jostein Stuhaug <[email protected]>
  • Loading branch information
4 people authored Jan 7, 2025
1 parent 84a2d9e commit aedd46b
Show file tree
Hide file tree
Showing 15 changed files with 425 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import aksjonspunktCodes from '@fpsak-frontend/kodeverk/src/aksjonspunktCodes';
import fagsakYtelseType from '@fpsak-frontend/kodeverk/src/fagsakYtelseType';
import VedtakProsessIndex from '@fpsak-frontend/prosess-vedtak';
import { ProsessStegDef, ProsessStegPanelDef } from '@k9-sak-web/behandling-felles';
import { prosessStegCodes } from '@k9-sak-web/konstanter';

import { UngVedtakIndex } from '@k9-sak-web/gui/prosess/ung-vedtak/UngVedtakIndex.js';
import { konverterKodeverkTilKode } from '@k9-sak-web/lib/kodeverk/konverterKodeverkTilKode.js';
import { UngdomsytelseBehandlingApiKeys } from '../../data/ungdomsytelseBehandlingApi';
import findStatusForVedtak from '../vedtakStatusUtlederUngdomsytelse';

class PanelDef extends ProsessStegPanelDef {
getKomponent = props => <VedtakProsessIndex {...props} />;
getKomponent = props => {
const deepCopyProps = JSON.parse(JSON.stringify(props));
konverterKodeverkTilKode(deepCopyProps, false);
return <UngVedtakIndex {...props} {...deepCopyProps} />;
};

getAksjonspunktKoder = () => [
aksjonspunktCodes.FORESLA_VEDTAK,
Expand Down
2 changes: 1 addition & 1 deletion packages/v2/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
"dependencies": {
"@navikt/k9-klage-typescript-client": "1.0.20240513162434-997f3da",
"@navikt/k9-sak-typescript-client": "1.0.20241028110915",
"@navikt/ung-sak-typescript-client": "0.1.20241112140012"
"@navikt/ung-sak-typescript-client": "0.1.20250107115905"
}
}
8 changes: 8 additions & 0 deletions packages/v2/gui/src/app/UngSakClientContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@ import type { ApiRequestOptions } from '@k9-sak-web/backend/ungsak/generated';
import { UngSakClient } from '@k9-sak-web/backend/ungsak/generated';
import { createContext } from 'react';

// Current client generator hardcode accept: application/json into every request, which causes requests for binary content
// (pdf) to be denied by the server. To work around this until client generator works properly, we override the accept
// header manually for the few requests that don't serve json.
// TODO Remove when generated typescript client can set correct Accept header by itself.
const acceptHeaderOverrideWorkaround = (options: ApiRequestOptions<Record<string, string>>): Record<string, string> =>
options.url === '/formidling/vedtaksbrev/forhaandsvis' ? { Accept: 'application/pdf' } : {};

const headerResolver = async (options: ApiRequestOptions<Record<string, string>>): Promise<Record<string, string>> => {
const { headerName, headerValue } = generateNavCallidHeader();
const { xJsonSerializerOptionHeader, xJsonSerializerOptionValue } = jsonSerializerOption;
return {
...options.headers,
...acceptHeaderOverrideWorkaround(options),
[headerName]: headerValue, // Legg til nav call id header
[xJsonSerializerOptionHeader]: xJsonSerializerOptionValue, // Legg til X-Json-Serializer-Option header
};
Expand Down
55 changes: 55 additions & 0 deletions packages/v2/gui/src/prosess/ung-vedtak/AvslagsårsakListe.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { vilkårStatus } from '@k9-sak-web/backend/k9sak/kodeverk/behandling/VilkårStatus.js';
import { useKodeverkContext } from '@k9-sak-web/gui/kodeverk/index.js';
import { KodeverkType } from '@k9-sak-web/lib/kodeverk/types.js';
import { BodyShort } from '@navikt/ds-react';
import type { UngVedtakVilkårDto, UngVedtakVilkårPeriodeDto } from './UngVedtakVilkårDto';

const finnUnikeAvslagskoder = (avslåttePerioder: UngVedtakVilkårPeriodeDto[] = []) => {
const funnedeAvslagskoder = new Set();
const unikeAvslagskoder = avslåttePerioder.filter(el => {
const erDuplikat = funnedeAvslagskoder.has(el.avslagKode);
funnedeAvslagskoder.add(el.avslagKode);
return !erDuplikat;
});
return unikeAvslagskoder;
};

interface AvslagsårsakListeProps {
vilkår: UngVedtakVilkårDto[];
}

const AvslagsårsakListe = ({ vilkår }: AvslagsårsakListeProps) => {
const { kodeverkNavnFraKode, kodeverkNavnFraUndertypeKode } = useKodeverkContext();

const visAvslåtteVilkårsperioder = (avslåttVilkår: UngVedtakVilkårDto) => {
const avslåttePerioder = avslåttVilkår?.perioder?.filter(
periode => periode.vilkarStatus === vilkårStatus.IKKE_OPPFYLT,
);
const avslåttePerioderMedUnikeAvslagskoder = finnUnikeAvslagskoder(avslåttePerioder);

return avslåttePerioderMedUnikeAvslagskoder.map(avslåttPeriode => (
<BodyShort size="small" key={avslåttPeriode.avslagKode}>
{[
kodeverkNavnFraKode(avslåttVilkår.vilkarType, KodeverkType.VILKAR_TYPE),
': ',
kodeverkNavnFraUndertypeKode(
avslåttVilkår.vilkarType,
avslåttPeriode.avslagKode || '',
KodeverkType.AVSLAGSARSAK,
),
].join('')}
</BodyShort>
));
};

const avslatteVilkar = vilkår.filter(
v => Array.isArray(v.perioder) && v.perioder.some(periode => periode.vilkarStatus === vilkårStatus.IKKE_OPPFYLT),
);
if (avslatteVilkar.length === 0) {
return <BodyShort>Søker har ikke noen gyldig uttaksperiode</BodyShort>;
}

return <>{avslatteVilkar.map(avslåttVilkår => visAvslåtteVilkårsperioder(avslåttVilkår))}</>;
};

export default AvslagsårsakListe;
96 changes: 96 additions & 0 deletions packages/v2/gui/src/prosess/ung-vedtak/UngVedtak.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { behandlingType } from '@k9-sak-web/backend/ungsak/generated';
import alleKodeverkV2 from '@k9-sak-web/lib/kodeverk/mocks/alleKodeverkV2.json';
import type { Meta, StoryObj } from '@storybook/react';
import { KodeverkProvider } from '../../kodeverk';
import { asyncAction } from '../../storybook/asyncAction';
import { FakeUngVedtakBackendApi } from '../../storybook/mocks/FakeUngVedtakBackendApi';
import { UngVedtak } from './UngVedtak';

const api = new FakeUngVedtakBackendApi();
const meta = {
title: 'gui/prosess/ung-vedtak/UngVedtak.tsx',
args: {
aksjonspunkter: [],
api,
submitCallback: asyncAction('button-click'),
},
component: UngVedtak,
render: props => (
<KodeverkProvider
behandlingType={behandlingType.BT_002}
kodeverk={alleKodeverkV2}
klageKodeverk={{}}
tilbakeKodeverk={{}}
>
<UngVedtak {...props} />
</KodeverkProvider>
),
} satisfies Meta<typeof UngVedtak>;
export default meta;

type Story = StoryObj<typeof meta>;

export const InnvilgetStory: Story = {
args: {
behandling: {
behandlingsresultat: {
type: 'INNVILGET',
},
id: 3000002,
},
vilkår: [
{
vilkarType: 'UNG_VK_XXX',
perioder: [
{
avslagKode: null,

vilkarStatus: 'OPPFYLT',
},
],
},
{
vilkarType: 'K9_VK_3',
perioder: [
{
avslagKode: null,
vilkarStatus: 'OPPFYLT',
},
],
},
],
readOnly: false,
},
};

export const AvslåttStory: Story = {
args: {
vilkår: [
{
vilkarType: 'UNG_VK_XXX',
perioder: [
{
avslagKode: null,
vilkarStatus: 'OPPFYLT',
},
],
},
{
vilkarType: 'K9_VK_3',
perioder: [
{
avslagKode: '1090',
vilkarStatus: 'IKKE_OPPFYLT',
},
],
},
],
behandling: {
behandlingsresultat: {
type: 'AVSLÅTT',
},
id: 3000001,
},
readOnly: false,
},
};
130 changes: 130 additions & 0 deletions packages/v2/gui/src/prosess/ung-vedtak/UngVedtak.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { behandlingResultatType, type AksjonspunktDto } from '@k9-sak-web/backend/ungsak/generated';
import { FileSearchIcon } from '@navikt/aksel-icons';
import { BodyShort, Box, Button, Fieldset, HStack, Label, VStack } from '@navikt/ds-react';
import { CheckboxField, Form } from '@navikt/ft-form-hooks';
import { useQuery } from '@tanstack/react-query';
import { useForm, useWatch } from 'react-hook-form';
import AvslagsårsakListe from './AvslagsårsakListe';
import styles from './ungVedtak.module.css';
import type { UngVedtakBackendApiType } from './UngVedtakBackendApiType';
import type { UngVedtakBehandlingDto } from './UngVedtakBehandlingDto';
import type { UngVedtakVilkårDto } from './UngVedtakVilkårDto';

interface UngVedtakProps {
aksjonspunkter: AksjonspunktDto[];
api: UngVedtakBackendApiType;
behandling: UngVedtakBehandlingDto;
submitCallback: (data: any) => Promise<any>;
vilkår: UngVedtakVilkårDto[];
readOnly: boolean;
}

const buildInitialValues = () => ({
redigerAutomatiskBrev: false,
hindreUtsendingAvBrev: false,
});

interface FormData {
redigerAutomatiskBrev: boolean;
hindreUtsendingAvBrev: boolean;
}

export const UngVedtak = ({ api, behandling, aksjonspunkter, submitCallback, vilkår, readOnly }: UngVedtakProps) => {
const formMethods = useForm<FormData>({
defaultValues: buildInitialValues(),
});
const behandlingErInnvilget = behandling.behandlingsresultat?.type === behandlingResultatType.INNVILGET;
const behandlingErAvslått = behandling.behandlingsresultat?.type === behandlingResultatType.AVSLÅTT;
const harAksjonspunkt = aksjonspunkter.filter(ap => ap.kanLoses).length > 0;
const redigerAutomatiskBrev = useWatch({ control: formMethods.control, name: 'redigerAutomatiskBrev' });
const hindreUtsendingAvBrev = useWatch({ control: formMethods.control, name: 'hindreUtsendingAvBrev' });

const { refetch, isLoading: forhåndsvisningIsLoading } = useQuery({
queryKey: ['forhandsvisVedtaksbrev', behandling.id],
queryFn: async () => {
const response = await api.forhåndsvisVedtaksbrev(behandling.id);
// Create a URL object from the PDF blob
const fileURL = window.URL.createObjectURL(response);
// Open the PDF in a new tab
window.open(fileURL, '_blank');
return response;
},
enabled: false,
});

const { data: tilgjengeligeVedtaksbrev, isLoading: tilgjengeligeVedtaksbrevIsLoading } = useQuery({
queryKey: ['tilgjengeligeVedtaksbrev', behandling.id],
queryFn: async () => {
const response = await api.tilgjengeligeVedtaksbrev(behandling.id);
return response;
},
});

const transformValues = () => aksjonspunkter.filter(ap => ap.kanLoses).map(ap => ({ kode: ap.definisjon }));
const handleSubmit = () => {
submitCallback(transformValues());
};

return (
<Form formMethods={formMethods} onSubmit={handleSubmit}>
<Box marginBlock="4">
<HStack justify="space-between">
<VStack gap="4">
<div>
<Label size="small" as="p">
Resultat
</Label>
<BodyShort size="small">
{behandlingErInnvilget ? 'Ungdomsytelse er innvilget' : 'Ungdomsytelse er avslått'}
</BodyShort>
</div>
{behandlingErAvslått && (
<div>
<Label size="small" as="p">
Årsak til avslag
</Label>
<AvslagsårsakListe vilkår={vilkår} />
</div>
)}
<div>
<Button
variant="tertiary"
onClick={() => refetch()}
size="small"
icon={<FileSearchIcon aria-hidden fontSize="1.5rem" />}
loading={forhåndsvisningIsLoading}
type="button"
disabled={!tilgjengeligeVedtaksbrev?.harBrev || tilgjengeligeVedtaksbrevIsLoading}
>
Forhåndsvis brev
</Button>
</div>
{harAksjonspunkt && (
<div>
<Button type="submit" variant="primary" size="small">
Fatt vedtak
</Button>
</div>
)}
</VStack>
<div className={styles.brevCheckboxContainer}>
<Fieldset legend="Valg for brev" size="small">
<div>
<CheckboxField
name="redigerAutomatiskBrev"
label="Rediger automatisk brev"
disabled={hindreUtsendingAvBrev || readOnly}
/>
<CheckboxField
name="hindreUtsendingAvBrev"
label="Hindre utsending av brev"
disabled={redigerAutomatiskBrev || readOnly}
/>
</div>
</Fieldset>
</div>
</HStack>
</Box>
</Form>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type {
ForhåndsvisVedtaksbrevResponse,
TilgjengeligeVedtaksbrevResponse,
} from '@k9-sak-web/backend/ungsak/generated';

export type UngVedtakBackendApiType = {
forhåndsvisVedtaksbrev(behandlingUuid: number): Promise<ForhåndsvisVedtaksbrevResponse>;
tilgjengeligeVedtaksbrev(behandlingUuid: number): Promise<TilgjengeligeVedtaksbrevResponse>;
};
21 changes: 21 additions & 0 deletions packages/v2/gui/src/prosess/ung-vedtak/UngVedtakBackendClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type {
ForhåndsvisVedtaksbrevResponse,
TilgjengeligeVedtaksbrevResponse,
UngSakClient,
} from '@k9-sak-web/backend/ungsak/generated';

export default class UngVedtakBackendClient {
#ungsak: UngSakClient;

constructor(ungsakClient: UngSakClient) {
this.#ungsak = ungsakClient;
}

async forhåndsvisVedtaksbrev(behandlingId: number): Promise<ForhåndsvisVedtaksbrevResponse> {
return this.#ungsak.formidling.forhåndsvisVedtaksbrev({ behandlingId });
}

async tilgjengeligeVedtaksbrev(behandlingId: number): Promise<TilgjengeligeVedtaksbrevResponse> {
return this.#ungsak.formidling.tilgjengeligeVedtaksbrev(`${behandlingId}`);
}
}
10 changes: 10 additions & 0 deletions packages/v2/gui/src/prosess/ung-vedtak/UngVedtakBehandlingDto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { BehandlingsresultatDto } from '@k9-sak-web/backend/ungsak/generated';

type Behandlingsresultat = {
type: BehandlingsresultatDto['type'];
};

export type UngVedtakBehandlingDto = {
behandlingsresultat: Behandlingsresultat;
id: number;
};
Loading

0 comments on commit aedd46b

Please sign in to comment.