diff --git a/src/Root.tsx b/src/Root.tsx index a44aaaa8f..c352f1f3b 100644 --- a/src/Root.tsx +++ b/src/Root.tsx @@ -47,6 +47,7 @@ const ManuellReguleringPage = React.lazy(() => import('~src/pages/saksbehandling const Stans = React.lazy(() => import('~src/pages/saksbehandling/stans/Stans')); const Gjenoppta = React.lazy(() => import('~src/pages/saksbehandling/gjenoppta/Gjenoppta')); const Utenlandsopphold = React.lazy(() => import('~src/pages/saksbehandling/utenlandsopphold/Utenlandsopphold')); +const Tilbakekreving = React.lazy(() => import('~src/pages/saksbehandling/tilbakekreving/Tilbakekreving')); const ScrollToTop = () => { const { pathname } = useLocation(); @@ -110,6 +111,8 @@ const AppRoutes = () => ( } /> } /> } /> + + } /> res.blob(), }); } + +export async function fetchBrevutkastForForhåndsvarselTilbakekreving(arg: { + sakId: string; + behandlingId: string; + fritekst: string; +}): Promise> { + return apiClient({ + url: `/saker/${arg.sakId}/tilbakekreving/${arg.behandlingId}/brevutkastForForhandsvarsel`, + method: 'POST', + request: { headers: new Headers({ Accept: 'application/pdf' }) }, + body: { + brevtekst: arg.fritekst, + }, + bodyTransformer: (res) => res.blob(), + }); +} diff --git a/src/api/tilbakekrevingApi.ts b/src/api/tilbakekrevingApi.ts new file mode 100644 index 000000000..f0d50517d --- /dev/null +++ b/src/api/tilbakekrevingApi.ts @@ -0,0 +1,60 @@ +import { + BrevtekstTilbakekrevingsbehandlingRequest, + ForhåndsvarsleTilbakekrevingRequest, + ManuellTilbakekrevingsbehandling, + OpprettNyTilbakekrevingsbehandlingRequest, + VurderTilbakekrevingsbehandlingRequest, +} from '~src/types/ManuellTilbakekrevingsbehandling'; + +import apiClient, { ApiClientResult } from './apiClient'; + +export async function opprettNyTilbakekrevingsbehandling( + arg: OpprettNyTilbakekrevingsbehandlingRequest, +): Promise> { + return apiClient({ + url: `/saker/${arg.sakId}/tilbakekreving/ny`, + method: 'POST', + body: { + saksversjon: arg.saksversjon, + }, + }); +} + +export async function vurderTilbakekrevingsbehandling( + arg: VurderTilbakekrevingsbehandlingRequest, +): Promise> { + return apiClient({ + url: `/saker/${arg.sakId}/tilbakekreving/${arg.behandlingId}/manedsvurder`, + method: 'POST', + body: { + versjon: arg.saksversjon, + måneder: arg.måneder, + }, + }); +} + +export async function sendForhåndsvarsel( + arg: ForhåndsvarsleTilbakekrevingRequest, +): Promise> { + return apiClient({ + url: `/saker/${arg.sakId}/tilbakekreving/${arg.behandlingId}/forhandsvarsel`, + method: 'POST', + body: { + versjon: arg.saksversjon, + fritekst: arg.fritekst, + }, + }); +} + +export async function brevtekstTilbakekrevingsbehandling( + arg: BrevtekstTilbakekrevingsbehandlingRequest, +): Promise> { + return apiClient({ + url: `/saker/${arg.sakId}/tilbakekreving/${arg.behandlingId}/brevtekst`, + method: 'POST', + body: { + versjon: arg.saksversjon, + brevtekst: arg.brevtekst, + }, + }); +} diff --git a/src/components/forms/attesteringForm/AttesteringsForm.tsx b/src/components/forms/attesteringForm/AttesteringsForm.tsx index 40cf2c774..3417c7f3b 100644 --- a/src/components/forms/attesteringForm/AttesteringsForm.tsx +++ b/src/components/forms/attesteringForm/AttesteringsForm.tsx @@ -1,6 +1,6 @@ import * as RemoteData from '@devexperts/remote-data-ts'; import { yupResolver } from '@hookform/resolvers/yup'; -import { Button, Loader, Radio, RadioGroup } from '@navikt/ds-react'; +import { Button, Radio, RadioGroup } from '@navikt/ds-react'; import React from 'react'; import { Controller, useForm } from 'react-hook-form'; @@ -111,10 +111,13 @@ export const AttesteringsForm = (props: Props) => { > {formatMessage('knapp.tilbake')} -
diff --git a/src/components/navigasjonsknapper/Navigasjonsknapper.tsx b/src/components/navigasjonsknapper/Navigasjonsknapper.tsx index 6e795dd47..76f063bab 100644 --- a/src/components/navigasjonsknapper/Navigasjonsknapper.tsx +++ b/src/components/navigasjonsknapper/Navigasjonsknapper.tsx @@ -51,7 +51,7 @@ const Navigasjonsknapper = (props: { variant="secondary" onClick={() => { setKnappTrykket('avslutt'); - props.fortsettSenere!.onClick!(); + props.fortsettSenere?.onClick?.(); }} type="button" loading={props.fortsettSenere?.loading ?? (knappTrykket === 'avslutt' && props.neste?.loading)} diff --git a/src/components/oppsummering/kravgrunnlag/OppsummeringAvKravgrunnlag-nb.ts b/src/components/oppsummering/kravgrunnlag/OppsummeringAvKravgrunnlag-nb.ts new file mode 100644 index 000000000..4ce68f5d5 --- /dev/null +++ b/src/components/oppsummering/kravgrunnlag/OppsummeringAvKravgrunnlag-nb.ts @@ -0,0 +1,18 @@ +export default { + 'kravgrunnlag.tittel': 'Oppsummering av kravgrunnlag', + 'kravgrunnlag.id': 'OS kravgrunnlags-id', + 'kravgrunnlag.vedtakId': 'OS vedtak-id', + 'kravgrunnlag.status': 'Status', + 'kravgrunnlag.kontrollfelt': 'Kontrollfelt', + + 'kravgrunnlag.grunnlagsperiode.tittel': 'Grunnlagsperioder', + 'kravgrunnlag.grunnlagsperiode.periode': 'Periode', + 'kravgrunnlag.grunnlagsperiode.beløpSkattMnd': 'Skatt per måned', + 'kravgrunnlag.grunnlagsperiode.beløp.kode': 'Kode', + 'kravgrunnlag.grunnlagsperiode.beløp.type': 'Type', + 'kravgrunnlag.grunnlagsperiode.beløp.skatteProsent': 'Skatteprosent', + 'kravgrunnlag.grunnlagsperiode.beløp.beløpTidligereUtbetaling': 'Tidligere utbetalt', + 'kravgrunnlag.grunnlagsperiode.beløp.beløpNyUtbetaling': 'Ny utbetaling', + 'kravgrunnlag.grunnlagsperiode.beløp.beløpSkalTilbakekreves': 'Skal tilbakekreves', + 'kravgrunnlag.grunnlagsperiode.beløp.beløpSkalIkkeTilbakekreves': 'Skal ikke tilbakekreves', +}; diff --git a/src/components/oppsummering/kravgrunnlag/OppsummeringAvKravgrunnlag.module.less b/src/components/oppsummering/kravgrunnlag/OppsummeringAvKravgrunnlag.module.less new file mode 100644 index 000000000..d4975a3a3 --- /dev/null +++ b/src/components/oppsummering/kravgrunnlag/OppsummeringAvKravgrunnlag.module.less @@ -0,0 +1,19 @@ +@import '~/src/styles/variables.less'; + +.kravgrunnlagOppsummeringContainer { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: @spacing-s; + margin-bottom: @spacing; +} + +.grunnlagsbeløperContainer { + list-style-type: none; + margin-bottom: @spacing-s; + + .grunnlagsbeløpContainer { + display: grid; + grid-template-columns: repeat(2, 1fr); + margin-bottom: @spacing-xxs; + } +} diff --git a/src/components/oppsummering/kravgrunnlag/OppsummeringAvKravgrunnlag.tsx b/src/components/oppsummering/kravgrunnlag/OppsummeringAvKravgrunnlag.tsx new file mode 100644 index 000000000..f9495a5a5 --- /dev/null +++ b/src/components/oppsummering/kravgrunnlag/OppsummeringAvKravgrunnlag.tsx @@ -0,0 +1,149 @@ +import { Heading, Accordion } from '@navikt/ds-react'; +import AccordionItem from '@navikt/ds-react/esm/accordion/AccordionItem'; +import React from 'react'; + +import Oppsummeringspanel, { + Oppsummeringsikon, + Oppsummeringsfarge, +} from '~src/components/oppsummeringspanel/Oppsummeringspanel'; +import { useI18n } from '~src/lib/i18n'; +import { Kravgrunnlag, Grunnlagsperiode } from '~src/types/Kravgrunnlag'; +import { formatMonthYear } from '~src/utils/date/dateUtils'; + +import { OppsummeringPar } from '../oppsummeringpar/OppsummeringPar'; + +import messages from './OppsummeringAvKravgrunnlag-nb'; +import styles from './OppsummeringAvKravgrunnlag.module.less'; + +const OppsummeringAvKravgrunnlag = (props: { + kravgrunnlag: Kravgrunnlag; + visSomEnkeltPanel?: boolean; + bareOppsummerMetaInfo?: boolean; +}) => { + const { formatMessage } = useI18n({ messages }); + + if (props.bareOppsummerMetaInfo) { + return ; + } else if (props.visSomEnkeltPanel) { + return ( +
+ + +
+ ); + } else { + return ( + + + + + ); + } +}; + +const OppsummeringAvKravgrunnlagMetaInfo = (props: { kravgrunnlag: Kravgrunnlag }) => { + const { formatMessage } = useI18n({ messages }); + return ( +
+ + + + + +
+ ); +}; + +const OppsummeringAvGrunnlagsPerioder = (props: { grunnlagsperiode: Grunnlagsperiode[] }) => { + const { formatMessage } = useI18n({ messages }); + return ( +
+ {formatMessage('kravgrunnlag.grunnlagsperiode.tittel')} + + + {props.grunnlagsperiode.map((periode) => ( + + + {`${formatMonthYear(periode.periode.fraOgMed)} - ${formatMonthYear( + periode.periode.tilOgMed, + )}`} + + + +
+ +
+
+
+
+ +
+ +
+ + +
+
+ + +
+
+
+
+ ))} +
+
+ ); +}; + +export default OppsummeringAvKravgrunnlag; diff --git a/src/components/tabell/SuTabellUtils.ts b/src/components/tabell/SuTabellUtils.ts index f474b2f14..7e4c6f795 100644 --- a/src/components/tabell/SuTabellUtils.ts +++ b/src/components/tabell/SuTabellUtils.ts @@ -1,5 +1,6 @@ import { Nullable } from '~src/lib/types'; import { Klage } from '~src/types/Klage'; +import { ManuellTilbakekrevingsbehandling } from '~src/types/ManuellTilbakekrevingsbehandling'; import { Regulering } from '~src/types/Regulering'; import { Revurdering } from '~src/types/Revurdering'; import { Søknad } from '~src/types/Søknad'; @@ -20,9 +21,16 @@ export const isRegulering = (b: TabellBehandling): b is Regulering => 'regulerin export const isSøknadMedEllerUtenBehandling = (b: TabellBehandling): b is SøknadMedEllerUtenBehandling => 'søknad' in b; export const isRevurdering = (b: TabellBehandling): b is Revurdering => 'årsak' in b; export const isKlage = (b: TabellBehandling): b is Klage => 'klagevedtakshistorikk' in b; +export const isManuellTilbakekrevingsbehandling = (b: TabellBehandling): b is ManuellTilbakekrevingsbehandling => + 'kravgrunnlag' in b; export type SøknadMedEllerUtenBehandling = { søknad: Søknad; søknadsbehandling?: Søknadsbehandling }; -export type TabellBehandling = SøknadMedEllerUtenBehandling | Revurdering | Klage | Regulering; +export type TabellBehandling = + | SøknadMedEllerUtenBehandling + | Revurdering + | Klage + | Regulering + | ManuellTilbakekrevingsbehandling; export type TabellBehandlinger = TabellBehandling[]; export type DatacellStatus = @@ -36,12 +44,13 @@ export type DatacellStatus = | 'Underkjent' | 'Iverksatt' | 'Avsluttet' - | 'Oversendt'; + | 'Oversendt' + | 'Vurdert'; export type DataCellResultat = '-' | 'Avslag' | 'Innvilget' | 'Avvist' | 'Til vurdering' | 'Opphør' | 'Endring'; export interface DataCellInfo { - type: 'søknad' | 'regulering' | 'revurdering' | 'klage' | 'stans' | 'gjenopptak'; + type: 'søknad' | 'regulering' | 'revurdering' | 'klage' | 'stans' | 'gjenopptak' | 'tilbakekreving'; status: DatacellStatus; resultat: DataCellResultat; periode: string; @@ -101,5 +110,31 @@ export const getDataCellInfo = (b: TabellBehandling): DataCellInfo => { avsluttetTidspunkt: b.avsluttetTidspunkt, }; } + + if (isManuellTilbakekrevingsbehandling(b)) { + return { + type: 'tilbakekreving', + status: (() => { + switch (b.status) { + case 'OPPRETTET': + return 'Opprettet'; + case 'VURDERT_UTEN_BREV': + return 'Vurdert'; + case 'VURDERT_MED_BREV': + return 'Vurdert'; + case 'TIL_ATTESTERING': + return 'Til attestering'; + case 'IVERKSATT': + return 'Iverksatt'; + } + throw new Error('Ukjent status for tilbakekreving'); + })(), + resultat: '-', + periode: '-', + mottattOpprettetTidspunkt: b.opprettet, + avsluttetTidspunkt: null, + }; + } + throw new Error('Feil ved mapping av behandling til dataCellInfo'); }; diff --git a/src/features/TilbakekrevingActions.ts b/src/features/TilbakekrevingActions.ts new file mode 100644 index 000000000..9e4391139 --- /dev/null +++ b/src/features/TilbakekrevingActions.ts @@ -0,0 +1,62 @@ +import { createAsyncThunk } from '@reduxjs/toolkit'; + +import { ApiError } from '~src/api/apiClient'; +import * as tilbakekrevingsApi from '~src/api/tilbakekrevingApi'; +import { + BrevtekstTilbakekrevingsbehandlingRequest, + ForhåndsvarsleTilbakekrevingRequest, + ManuellTilbakekrevingsbehandling, + OpprettNyTilbakekrevingsbehandlingRequest, + VurderTilbakekrevingsbehandlingRequest, +} from '~src/types/ManuellTilbakekrevingsbehandling'; + +export const opprettNyTilbakekrevingsbehandling = createAsyncThunk< + ManuellTilbakekrevingsbehandling, + OpprettNyTilbakekrevingsbehandlingRequest, + { rejectValue: ApiError } +>('tilbakekreving/opprett', async ({ sakId, saksversjon }, thunkApi) => { + const res = await tilbakekrevingsApi.opprettNyTilbakekrevingsbehandling({ + sakId, + saksversjon, + }); + if (res.status === 'ok') { + return res.data; + } + return thunkApi.rejectWithValue(res.error); +}); + +export const vurderTilbakekrevingsbehandling = createAsyncThunk< + ManuellTilbakekrevingsbehandling, + VurderTilbakekrevingsbehandlingRequest, + { rejectValue: ApiError } +>('tilbakekreving/vurder', async (args, thunkApi) => { + const res = await tilbakekrevingsApi.vurderTilbakekrevingsbehandling(args); + if (res.status === 'ok') { + return res.data; + } + return thunkApi.rejectWithValue(res.error); +}); + +export const sendForhåndsvarsel = createAsyncThunk< + ManuellTilbakekrevingsbehandling, + ForhåndsvarsleTilbakekrevingRequest, + { rejectValue: ApiError } +>('tilbakekreving/forhåndsvarsel', async (args, thunkApi) => { + const res = await tilbakekrevingsApi.sendForhåndsvarsel(args); + if (res.status === 'ok') { + return res.data; + } + return thunkApi.rejectWithValue(res.error); +}); + +export const brevtekstTilbakekrevingsbehandling = createAsyncThunk< + ManuellTilbakekrevingsbehandling, + BrevtekstTilbakekrevingsbehandlingRequest, + { rejectValue: ApiError } +>('tilbakekreving/brev', async (args, thunkApi) => { + const res = await tilbakekrevingsApi.brevtekstTilbakekrevingsbehandling(args); + if (res.status === 'ok') { + return res.data; + } + return thunkApi.rejectWithValue(res.error); +}); diff --git a/src/features/saksoversikt/sak.slice.ts b/src/features/saksoversikt/sak.slice.ts index d6141c7a0..8baeb6bdf 100644 --- a/src/features/saksoversikt/sak.slice.ts +++ b/src/features/saksoversikt/sak.slice.ts @@ -10,11 +10,13 @@ import * as klageActions from '~src/features/klage/klageActions'; import * as revurderingActions from '~src/features/revurdering/revurderingActions'; import * as SøknadActions from '~src/features/søknad/SøknadActions'; import * as SøknadsbehandlingActions from '~src/features/SøknadsbehandlingActions'; +import * as tilbakekrevingActions from '~src/features/TilbakekrevingActions'; import { pipe } from '~src/lib/fp'; import { handleAsyncThunk, simpleRejectedActionToRemoteData } from '~src/redux/utils'; import { Behandlingssammendrag } from '~src/types/Behandlingssammendrag'; import { Dokument, DokumentIdType } from '~src/types/dokument/Dokument'; import { Klage } from '~src/types/Klage'; +import { ManuellTilbakekrevingsbehandling } from '~src/types/ManuellTilbakekrevingsbehandling'; import { OppdaterRegistrertUtenlandsoppholdRequest, RegistrerteUtenlandsopphold, @@ -362,6 +364,24 @@ export default createSlice({ builder.addCase(GrunnlagOgVilkårActions.lagreOpplysningsplikt.fulfilled, (state, action) => { state.sak = opprettEllerOppdaterRevurderingISak(state.sak, action.payload.revurdering); }); + + //---------------Tilbakekreving-----------------// + builder.addCase(tilbakekrevingActions.opprettNyTilbakekrevingsbehandling.fulfilled, (state, action) => { + state.sak = pipe( + state.sak, + RemoteData.map((s) => ({ + ...s, + tilbakekrevinger: [...s.tilbakekrevinger, action.payload], + })), + ); + }); + + builder.addCase(tilbakekrevingActions.vurderTilbakekrevingsbehandling.fulfilled, (state, action) => { + state.sak = oppdaterTilbakekrevingPåSak(state.sak, action.payload); + }); + builder.addCase(tilbakekrevingActions.brevtekstTilbakekrevingsbehandling.fulfilled, (state, action) => { + state.sak = oppdaterTilbakekrevingPåSak(state.sak, action.payload); + }); }, }); @@ -444,3 +464,16 @@ function oppdaterUtenlandsoppholdISak( })), ); } + +function oppdaterTilbakekrevingPåSak( + sak: RemoteData.RemoteData, + tilbakekreving: ManuellTilbakekrevingsbehandling, +) { + return pipe( + sak, + RemoteData.map((s) => ({ + ...s, + tilbakekrevinger: s.tilbakekrevinger.map((t) => (t.id === tilbakekreving.id ? tilbakekreving : t)), + })), + ); +} diff --git a/src/lib/hooks.ts b/src/lib/hooks.ts index 48df6b47e..5e50eb25b 100644 --- a/src/lib/hooks.ts +++ b/src/lib/hooks.ts @@ -1,6 +1,6 @@ import * as RemoteData from '@devexperts/remote-data-ts'; import { AsyncThunk } from '@reduxjs/toolkit'; -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { ApiClientResult, ApiError } from '~src/api/apiClient'; @@ -135,3 +135,56 @@ export const useExclusiveCombine = (...args: Array !RemoteData.isInitial(a)) ?? args[0]; }, [args]); }; + +/** + * useAutosave er for bruk dersom du vil kalle på en funksjon etter X sekunder, der endringer i dependencies resetter timeren. + * + * @param callback - funksjonen som skal kjøres for hver delay + * @param delay - tiden i millisekunder autosaven skal kjøres + * @param deps - Et set med dependencies som resetter timeren for hver endring + */ +export const useAutosave = (callback: () => void, delay = 5000, deps: unknown[] = []) => { + React.useEffect(() => { + if (delay) { + const interval = setInterval(callback, delay); + + return () => clearInterval(interval); + } + + return; + }, [delay, ...deps]); +}; + +/** + * useAutosaveOnChange er for bruk dersom du vil kalle på en funksjon hvert X sekund, & dersom data har endret på seg + * + * @param data - dataen som sjekkes på om ting har endret seg + * @param callback - funksjonen som skal kjøres + * @param delay - tiden i millisekunder autosaven skal kjøres + */ +export const useAutosaveOnChange = (data: T, callback: () => void, delay = 5000) => { + const [isSaving, setIsSaving] = React.useState(false); + const initialRender = React.useRef(true); + const prev = React.useRef(data); + const live = React.useRef(data); + + useAutosave(() => { + if (prev.current !== live.current) { + prev.current = live.current; + callback(); + } else { + setIsSaving(false); + } + }, delay); + + useEffect(() => { + if (initialRender.current) { + initialRender.current = false; + } else { + setIsSaving(true); + } + live.current = data; + }, [data]); + + return { isSaving }; +}; diff --git a/src/lib/routes.ts b/src/lib/routes.ts index b2b508326..dde631d59 100644 --- a/src/lib/routes.ts +++ b/src/lib/routes.ts @@ -1,7 +1,7 @@ import { NavigateFunction, useParams } from 'react-router-dom'; import * as Routes from '~src/lib/routes'; -import { KlageSteg, SaksbehandlingMenyvalg } from '~src/pages/saksbehandling/types'; +import { KlageSteg, SaksbehandlingMenyvalg, TilbakekrevingSteg } from '~src/pages/saksbehandling/types'; import { Søknadssteg } from '~src/pages/søknad/types'; import { RevurderingSeksjoner, RevurderingSteg } from '~src/types/Revurdering'; import { Sakstype } from '~src/types/Sak'; @@ -257,12 +257,6 @@ export const klage: Route<{ absPath: '/saksoversikt/:sakId/klage/:klageId/:steg/', createURL: (args) => `/saksoversikt/${args.sakId}/klage/${args.klageId}/${args.steg}/`, }; -//---------------Regulering------------------------- -export const manuellRegulering: Route<{ sakId: string; reguleringId: string }> = { - path: 'reguler/:reguleringId', - absPath: '/saksoversikt/:sakId/reguler/:reguleringId', - createURL: (args) => `/saksoversikt/${args.sakId}/reguler/${args.reguleringId}`, -}; export const klageOpprett: Route<{ sakId: string }> = { path: 'opprett', @@ -270,6 +264,13 @@ export const klageOpprett: Route<{ sakId: string }> = { createURL: ({ sakId }) => `/saksoversikt/${sakId}/klage/opprett`, }; +//---------------Regulering------------------------- +export const manuellRegulering: Route<{ sakId: string; reguleringId: string }> = { + path: 'reguler/:reguleringId', + absPath: '/saksoversikt/:sakId/reguler/:reguleringId', + createURL: (args) => `/saksoversikt/${args.sakId}/reguler/${args.reguleringId}`, +}; + export interface SuccessNotificationState { notification?: string; } @@ -309,3 +310,24 @@ export const brevPage: Route<{ sakId: string }> = { absPath: '/saksoversikt/:sakId/brev', createURL: (args) => `/saksoversikt/${args.sakId}/brev/`, }; + +//---------------Tilbakekreving------------------------- +export const tilbakekrevingRoot: Route<{ + sakId: string; +}> = { + path: 'tilbakekreving/*', + absPath: 'saksoversikt/:sakId/tilbakekreving', + createURL: ({ sakId }) => `/saksoversikt/${sakId}/tilbakekreving`, +}; + +export const tilbakekrevValgtSak: Route<{ sakId: string }> = { + path: 'opprett', + absPath: '/saksoversikt/:sakId/tilbakekreving/opprett', + createURL: (args) => `/saksoversikt/${args.sakId}/tilbakekreving/opprett`, +}; + +export const tilbakekrevingValgtBehandling: Route<{ sakId: string; behandlingId: string; steg: TilbakekrevingSteg }> = { + path: ':behandlingId/:steg', + absPath: '/saksoversikt/:sakId/tilbakekreving/:behandlingId/:steg', + createURL: (args) => `/saksoversikt/${args.sakId}/tilbakekreving/${args.behandlingId}/${args.steg}`, +}; diff --git a/src/pages/saksbehandling/sakintro/Sakintro.tsx b/src/pages/saksbehandling/sakintro/Sakintro.tsx index c6011fa47..e1544494e 100644 --- a/src/pages/saksbehandling/sakintro/Sakintro.tsx +++ b/src/pages/saksbehandling/sakintro/Sakintro.tsx @@ -1,7 +1,7 @@ import { ChevronUpIcon, ChevronDownIcon } from '@navikt/aksel-icons'; import { Alert, Button, LinkPanel, Popover } from '@navikt/ds-react'; import { isEmpty } from 'fp-ts/lib/Array'; -import React, { PropsWithChildren, useState } from 'react'; +import React, { useState } from 'react'; import { useOutletContext } from 'react-router-dom'; import { ÅpentBrev } from '~src/assets/Illustrations'; @@ -16,6 +16,7 @@ import Utbetalinger from '~src/pages/saksbehandling/sakintro/Utbetalinger'; import { KlageStatus } from '~src/types/Klage'; import { Sakstype } from '~src/types/Sak'; import { erKlageAvsluttet, erKlageÅpen } from '~src/utils/klage/klageUtils'; +import { erTilbakekrevingsbehandlingÅpen } from '~src/utils/ManuellTilbakekrevingsbehandlingUtils'; import { erReguleringAvsluttet, erReguleringÅpen } from '~src/utils/ReguleringUtils'; import { erRevurderingAvsluttet, erRevurderingÅpen } from '~src/utils/revurdering/revurderingUtils'; import { getIverksatteInnvilgedeSøknader, erSøknadLukket, erSøknadÅpen } from '~src/utils/søknad/søknadUtils'; @@ -38,6 +39,7 @@ const SuksessStatuser = (props: { locationState: Nullable { @@ -45,15 +47,6 @@ const Sakintro = () => { const { formatMessage } = useI18n({ messages }); const locationState = useNotificationFromLocation(); - const nyBehandlingTilRoute = (nyBehandling: NyBehandling): string => { - switch (nyBehandling) { - case NyBehandling.REVURDER: - return Routes.revurderValgtSak.createURL({ sakId: props.sak.id }); - case NyBehandling.KLAGE: - return Routes.klageOpprett.createURL({ sakId: props.sak.id }); - } - }; - const iverksatteInnvilgedeSøknader = getIverksatteInnvilgedeSøknader(props.sak); const harUtbetalinger = !isEmpty(props.sak.utbetalinger); @@ -89,7 +82,15 @@ const Sakintro = () => { return { søknad: åpenSøknad, søknadsbehandling: søknadsbehandling }; }); - const alleÅpneBehandlinger = [...åpneRevurderinger, ...åpneReguleringer, ...åpneSøknader, ...åpneKlager]; + const åpneTilbakekrevingsbehandlinger = props.sak.tilbakekrevinger.filter(erTilbakekrevingsbehandlingÅpen); + + const alleÅpneBehandlinger = [ + ...åpneRevurderinger, + ...åpneReguleringer, + ...åpneSøknader, + ...åpneKlager, + ...åpneTilbakekrevingsbehandlinger, + ]; return (
@@ -97,24 +98,10 @@ const Sakintro = () => {
{harVedtak && props.sak.sakstype !== Sakstype.Alder && ( - - {iverksatteInnvilgedeSøknader.length > 0 && ( - - {formatMessage('popover.option.revurder')} - - )} - - {formatMessage('popover.option.klage')} - - + 0} + /> )} {harUtbetalinger && ( { ); }; -const NyBehandlingVelger: React.FC = (props) => { +const NyBehandlingVelger = (props: { sakId: string; kanRevurdere: boolean }) => { const { formatMessage } = useI18n({ messages }); const [anchorEl, setAnchorEl] = useState>(null); + const nyBehandlingTilRoute = (nyBehandling: NyBehandling): string => { + switch (nyBehandling) { + case NyBehandling.REVURDER: + return Routes.revurderValgtSak.createURL({ sakId: props.sakId }); + case NyBehandling.KLAGE: + return Routes.klageOpprett.createURL({ sakId: props.sakId }); + case NyBehandling.TILBAKEKREVING: + return Routes.tilbakekrevValgtSak.createURL({ sakId: props.sakId }); + } + }; + return (
); diff --git a/src/pages/saksbehandling/sakintro/sakintro-nb.ts b/src/pages/saksbehandling/sakintro/sakintro-nb.ts index ff3589cfa..0cb54aae0 100644 --- a/src/pages/saksbehandling/sakintro/sakintro-nb.ts +++ b/src/pages/saksbehandling/sakintro/sakintro-nb.ts @@ -16,6 +16,7 @@ export default { 'popover.default': 'Velg behandling', 'popover.option.klage': 'Klage', 'popover.option.revurder': 'Revurder', + 'popover.option.tilbakekreving': 'Tilbakekreving', 'link.dokumenter': 'Brev sendt fra SU', 'link.kontrollsamtale': 'Kontrollsamtale', @@ -55,6 +56,7 @@ export default { 'datacell.behandlingstype.klage': 'Klage', 'datacell.behandlingstype.stans': 'Stans', 'datacell.behandlingstype.gjenopptak': 'Gjenopptak', + 'datacell.behandlingstype.tilbakekreving': 'Tilbakekreving', 'datacell.status.-': '-', 'datacell.status.nySøknad': 'Ny søknad', @@ -67,6 +69,7 @@ export default { 'datacell.status.Iverksatt': 'Iverksatt', 'datacell.status.Avsluttet': 'Avsluttet', 'datacell.status.Oversendt': 'Oversendt', + 'datacell.status.Vurdert': 'Vurdert', 'datacell.resultat.-': '-', 'datacell.resultat.Avslag': 'Avslag', diff --git "a/src/pages/saksbehandling/sakintro/\303\245pneBehandlingerTabell/\303\205pneBehandlingerTabell.tsx" "b/src/pages/saksbehandling/sakintro/\303\245pneBehandlingerTabell/\303\205pneBehandlingerTabell.tsx" index e6bfb42d4..5e8206a8d 100644 --- "a/src/pages/saksbehandling/sakintro/\303\245pneBehandlingerTabell/\303\205pneBehandlingerTabell.tsx" +++ "b/src/pages/saksbehandling/sakintro/\303\245pneBehandlingerTabell/\303\205pneBehandlingerTabell.tsx" @@ -17,6 +17,7 @@ import SuTabell, { AriaSortVerdi } from '~src/components/tabell/SuTabell'; import { getDataCellInfo, isKlage, + isManuellTilbakekrevingsbehandling, isRegulering, isRevurdering, isSøknadMedEllerUtenBehandling, @@ -31,11 +32,13 @@ import { useApiCall, useAsyncActionCreator } from '~src/lib/hooks'; import { useI18n } from '~src/lib/i18n'; import * as Routes from '~src/lib/routes'; import { Klage } from '~src/types/Klage'; +import { ManuellTilbakekrevingsbehandling } from '~src/types/ManuellTilbakekrevingsbehandling'; import { Regulering, Reguleringstype } from '~src/types/Regulering'; import { Revurdering } from '~src/types/Revurdering'; import { Vilkårtype } from '~src/types/Vilkårsvurdering'; import { formatDateTime } from '~src/utils/date/dateUtils'; import { erKlageTilAttestering, hentSisteVurderteSteg } from '~src/utils/klage/klageUtils'; +import { erTilbakekrevingTilAttestering } from '~src/utils/ManuellTilbakekrevingsbehandlingUtils'; import { erInformasjonsRevurdering, erRevurderingGjenopptak, @@ -49,6 +52,7 @@ import { kanNavigeresTilOppsummering, } from '~src/utils/SøknadsbehandlingUtils'; +import { TilbakekrevingSteg } from '../../types'; import messages from '../sakintro-nb'; import styles from './ÅpneBehandlingerTabell.module.less'; @@ -111,6 +115,9 @@ const ÅpneBehandlingerTabell = (props: { sakId: string; tabellBehandlinger: Tab {isRevurdering(props.b) && } {isKlage(props.b) && } {isRegulering(props.b) && } + {isManuellTilbakekrevingsbehandling(props.b) && ( + + )}
); }; @@ -444,3 +451,54 @@ const ReguleringKnapper = (props: { sakId: string; r: Regulering }) => {
); }; + +const TilbakekrevingsKnapper = (props: { sakId: string; t: ManuellTilbakekrevingsbehandling }) => { + const user = useUserContext(); + const { formatMessage } = useI18n({ messages }); + + if (erTilbakekrevingTilAttestering(props.t)) { + if (user.isAttestant && user.navIdent !== props.t.opprettetAv) { + return ( + + {formatMessage('attestering.attester')} + + ); + } + return <>; + } + + return ( +
+ + {formatMessage('datacell.info.knapp.avsluttBehandling')} + + + + {formatMessage('datacell.info.knapp.fortsettBehandling')} + +
+ ); +}; diff --git "a/src/pages/saksbehandling/s\303\270knadsbehandling/FormWrapper.tsx" "b/src/pages/saksbehandling/s\303\270knadsbehandling/FormWrapper.tsx" index e5b20eeaa..50b8f2e38 100644 --- "a/src/pages/saksbehandling/s\303\270knadsbehandling/FormWrapper.tsx" +++ "b/src/pages/saksbehandling/s\303\270knadsbehandling/FormWrapper.tsx" @@ -26,7 +26,7 @@ interface Props { savingState: ApiResult; onSuccess?: (res: U) => void; }; - tilbake?: { + tilbake: { url?: string; onClick?: () => void; }; diff --git a/src/pages/saksbehandling/tilbakekreving/Tilbakekreving-nb.ts b/src/pages/saksbehandling/tilbakekreving/Tilbakekreving-nb.ts new file mode 100644 index 000000000..c2e48d3e7 --- /dev/null +++ b/src/pages/saksbehandling/tilbakekreving/Tilbakekreving-nb.ts @@ -0,0 +1,31 @@ +export default { + 'tilbakekreving.tittel': 'Tilbakekreving', + + 'stegIndikator.vurdering': 'Vurdering', + 'stegIndikator.brev': 'Brev', + 'stegIndikator.forhandsvarsling': 'Forhåndsvarsel', + + 'vurderTilbakekreving.tittel': 'Vurdering av kravgrunnlag', + 'vurderTilbakekreving.skalBeløpBliTilbakekrevd': 'Skal beløpet tilbakekreves?', + 'vurderTilbakekreving.skalTilbakekreve': 'Beløpet skal tilbakekreves', + 'vurderTilbakekreving.skalIkkeTilbakekreve': 'Beløpet skal ikke tilbakekreves', + 'vurderTilbakekreving.feiloppsummering': 'Du må rette følgende feil', + 'vurderTilbakekreving.kravgrunnlagsInfo.skatteprosent': 'Skatteprosent', + 'vurderTilbakekreving.kravgrunnlagsInfo.tidligereUtbetalt': 'Tidligere utbetalt', + 'vurderTilbakekreving.kravgrunnlagsInfo.nyUtbetaling': 'Ny utbetaling', + 'vurderTilbakekreving.kravgrunnlagsInfo.skalTilbakekreves': 'Skal tilbakekreves', + 'vurderTilbakekreving.kravgrunnlagsInfo.skalIkkeTilbakekreves': 'Skal ikke tilbakekreves', + + 'forhåndsvarsleTilbakekreving.tittel': 'Forhåndsvarsling', + 'forhåndsvarsleTilbakekreving.skalForhåndsvarsle': 'Skal det forhåndsvarsles?', + 'forhåndsvarsleTilbakekreving.skalForhåndsvarsle.ja': 'Ja', + 'forhåndsvarsleTilbakekreving.skalForhåndsvarsle.nei': 'Nei', + 'forhåndsvarsleTilbakekreving.fritekst.label': 'Fritekst til brev', + 'forhåndsvarsleTilbakekreving.navigering.sendOgFortsett': 'Send og fortsett til saksoversikt', + 'forhåndsvarsleTilbakekreving.navigering.fortsettSenere': 'Fortsett senere', + + 'brevForTilbakekreving.tittel': 'Brev', + 'brevForTilbakekreving.fritekst.label': 'Fritekst til brev', + + 'knapp.seBrev': 'Se brev', +}; diff --git a/src/pages/saksbehandling/tilbakekreving/Tilbakekreving.tsx b/src/pages/saksbehandling/tilbakekreving/Tilbakekreving.tsx new file mode 100644 index 000000000..5aed9fd6b --- /dev/null +++ b/src/pages/saksbehandling/tilbakekreving/Tilbakekreving.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Route, Routes, useOutletContext } from 'react-router-dom'; + +import { SaksoversiktContext } from '~src/context/SaksoversiktContext'; +import * as routes from '~src/lib/routes'; + +import BehandleTilbakekreving from './behandleTilbakekreving/BehandleTilbakekreving'; +import OpprettTilbakekreving from './opprettTilbakekreving/OpprettTilbakekreving'; + +const Tilbakekreving = () => { + const { sak } = useOutletContext(); + + return ( + + + } + /> + + } + /> + + ); +}; + +export default Tilbakekreving; diff --git a/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/BehandleTilbakekreving.module.less b/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/BehandleTilbakekreving.module.less new file mode 100644 index 000000000..15f79592e --- /dev/null +++ b/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/BehandleTilbakekreving.module.less @@ -0,0 +1,16 @@ +@import '~/src/styles/variables.less'; + +.pageContainer { + width: 100%; +} + +.pageTittel { + width: 100%; + padding: @spacing-xs 13rem; + border-bottom: 1px solid @navBorder; +} + +.contentContainerMedFramdriftsindikator { + display: flex; + width: 100%; +} diff --git a/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/BehandleTilbakekreving.tsx b/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/BehandleTilbakekreving.tsx new file mode 100644 index 000000000..10fbafe5d --- /dev/null +++ b/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/BehandleTilbakekreving.tsx @@ -0,0 +1,73 @@ +import { Heading } from '@navikt/ds-react'; +import React from 'react'; + +import { useI18n } from '~src/lib/i18n'; +import * as routes from '~src/lib/routes'; +import { ManuellTilbakekrevingsbehandling } from '~src/types/ManuellTilbakekrevingsbehandling'; + +import { TilbakekrevingSteg } from '../../types'; +import messages from '../Tilbakekreving-nb'; + +import styles from './BehandleTilbakekreving.module.less'; +import BrevForTilbakekreving from './brevForTilbakekreving/BrevForTilbakekreving'; +import ForhåndsvarsleTilbakekreving from './forhåndsvarsleTilbakekreving/ForhåndsvarsleTilbakekreving'; +import TilbakekrevingStegIndikator from './TilbakekrevingStegIndikator'; +import VurderTilbakekreving from './vurderTilbakekreving/VurderTilbakekreving'; + +const BehandleTilbakekreving = (props: { + sakId: string; + saksversjon: number; + tilbakekrevinger: ManuellTilbakekrevingsbehandling[]; +}) => { + const { formatMessage } = useI18n({ messages }); + const { behandlingId, steg } = routes.useRouteParams(); + + const behandling = props.tilbakekrevinger.find((t) => t.id === behandlingId); + + if (!behandling) { + return ( +
+ Her skulle det visst vært en behandling, men var ingenting. Er URLen i riktig format, men en + eksisterende id? +
+ ); + } + + return ( +
+ + {formatMessage('tilbakekreving.tittel')} + +
+ + {steg === TilbakekrevingSteg.Vurdering && ( + + )} + {steg === TilbakekrevingSteg.Forhåndsvarsling && ( + + )} + {steg === TilbakekrevingSteg.Brev && ( + + )} +
+
+ ); +}; + +export default BehandleTilbakekreving; diff --git a/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/TilbakekrevingStegIndikator.tsx b/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/TilbakekrevingStegIndikator.tsx new file mode 100644 index 000000000..b94c8247d --- /dev/null +++ b/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/TilbakekrevingStegIndikator.tsx @@ -0,0 +1,61 @@ +import React from 'react'; + +import Framdriftsindikator, { Linjestatus } from '~src/components/framdriftsindikator/Framdriftsindikator'; +import { useI18n } from '~src/lib/i18n'; +import * as Routes from '~src/lib/routes'; +import { ManuellTilbakekrevingsbehandling } from '~src/types/ManuellTilbakekrevingsbehandling'; +import { + erTilbakekrevingForhåndsvarsletEllerSenere, + erTilbakekrevingVurdertMedBrevEllerSenere, + erTilbakekrevingVurdertUtenBrevEllerSenere, +} from '~src/utils/ManuellTilbakekrevingsbehandlingUtils'; + +import { TilbakekrevingSteg } from '../../types'; +import messages from '../Tilbakekreving-nb'; + +const stegsInformasjon = (behandling: ManuellTilbakekrevingsbehandling, steg: TilbakekrevingSteg) => { + switch (steg) { + case TilbakekrevingSteg.Vurdering: + return erTilbakekrevingVurdertUtenBrevEllerSenere(behandling) + ? { erKlikkbar: true, linjeStatus: Linjestatus.Ok } + : { erKlikkbar: false, linjeStatus: Linjestatus.Ingenting }; + case TilbakekrevingSteg.Forhåndsvarsling: + return erTilbakekrevingForhåndsvarsletEllerSenere(behandling) + ? { erKlikkbar: true, linjeStatus: Linjestatus.Ok } + : { erKlikkbar: false, linjeStatus: Linjestatus.Ingenting }; + case TilbakekrevingSteg.Brev: + return erTilbakekrevingVurdertMedBrevEllerSenere(behandling) + ? { erKlikkbar: true, linjeStatus: Linjestatus.Ok } + : { erKlikkbar: false, linjeStatus: Linjestatus.Ingenting }; + } +}; + +const TilbakekrevingStegIndikator = (props: { + sakId: string; + behandling: ManuellTilbakekrevingsbehandling; + aktivSteg: TilbakekrevingSteg; +}) => { + const { formatMessage } = useI18n({ messages }); + + return ( + { + const stegInfo = stegsInformasjon(props.behandling, steg); + return { + id: steg, + erKlikkbar: stegInfo.erKlikkbar, + label: formatMessage(`stegIndikator.${steg}`), + status: stegInfo.linjeStatus, + url: Routes.tilbakekrevingValgtBehandling.createURL({ + sakId: props.sakId, + behandlingId: props.behandling.id, + steg: steg, + }), + }; + })} + aktivId={props.aktivSteg} + /> + ); +}; + +export default TilbakekrevingStegIndikator; diff --git a/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/brevForTilbakekreving/BrevForTilbakekreving.module.less b/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/brevForTilbakekreving/BrevForTilbakekreving.module.less new file mode 100644 index 000000000..ec0268170 --- /dev/null +++ b/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/brevForTilbakekreving/BrevForTilbakekreving.module.less @@ -0,0 +1,20 @@ +@import '~/src/styles/variables.less'; + +.fritesktOgVisBrevContainer { + margin-bottom: @spacing; + + > :not(:last-child) { + margin-bottom: @spacing-xs; + } +} + +.textareaLabel { + //ganske så statisk verdi som setter loading ikonet på enden av textarea. Må sikkert endres dersom textarea endres + width: 400px; + display: flex; + justify-content: space-between; +} + +.seBrevButton { + width: @knappBredde; +} diff --git a/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/brevForTilbakekreving/BrevForTilbakekreving.tsx b/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/brevForTilbakekreving/BrevForTilbakekreving.tsx new file mode 100644 index 000000000..b0b3af228 --- /dev/null +++ b/src/pages/saksbehandling/tilbakekreving/behandleTilbakekreving/brevForTilbakekreving/BrevForTilbakekreving.tsx @@ -0,0 +1,161 @@ +import * as RemoteData from '@devexperts/remote-data-ts'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Button, Loader, Textarea } from '@navikt/ds-react'; +import React from 'react'; +import { Controller, UseFormTrigger, useForm } from 'react-hook-form'; +import { useNavigate } from 'react-router-dom'; + +import { ErrorIcon, SuccessIcon } from '~src/assets/Icons'; +import ApiErrorAlert from '~src/components/apiErrorAlert/ApiErrorAlert'; +import Feiloppsummering from '~src/components/feiloppsummering/Feiloppsummering'; +import Navigasjonsknapper from '~src/components/navigasjonsknapper/Navigasjonsknapper'; +import ToKolonner from '~src/components/toKolonner/ToKolonner'; +import { brevtekstTilbakekrevingsbehandling } from '~src/features/TilbakekrevingActions'; +import { useAsyncActionCreator, useAutosaveOnChange } from '~src/lib/hooks'; +import { useI18n } from '~src/lib/i18n'; +import * as routes from '~src/lib/routes'; +import { hookFormErrorsTilFeiloppsummering } from '~src/lib/validering'; +import { TilbakekrevingSteg } from '~src/pages/saksbehandling/types'; +import { ManuellTilbakekrevingsbehandling } from '~src/types/ManuellTilbakekrevingsbehandling'; + +import messages from '../../Tilbakekreving-nb'; + +import styles from './BrevForTilbakekreving.module.less'; +import { BrevForTilbakekrevingFormData, brevForTilbakekrevingSchema } from './BrevForTilbakekrevingUtils'; + +const BrevForTilbakekreving = (props: { + sakId: string; + saksversjon: number; + tilbakekreving: ManuellTilbakekrevingsbehandling; +}) => { + const navigate = useNavigate(); + const { formatMessage } = useI18n({ messages }); + const [brevStatus, lagreBrev] = useAsyncActionCreator(brevtekstTilbakekrevingsbehandling); + const [autosaveStatus, autosave] = useAsyncActionCreator(brevtekstTilbakekrevingsbehandling); + + const form = useForm({ + resolver: yupResolver(brevForTilbakekrevingSchema), + defaultValues: { + brevtekst: props.tilbakekreving.fritekst, + }, + }); + + const save = (data: BrevForTilbakekrevingFormData, onSuccess: () => void) => { + lagreBrev( + { + sakId: props.sakId, + saksversjon: props.saksversjon, + behandlingId: props.tilbakekreving.id, + brevtekst: data.brevtekst!, + }, + onSuccess, + ); + }; + + const handleLagreOgFortsettSenereClick = async ( + data: BrevForTilbakekrevingFormData, + trigger: UseFormTrigger, + ) => { + await trigger().then((isValid) => { + if (isValid) { + save(data, () => navigate(routes.saksoversiktValgtSak.createURL({ sakId: props.sakId }))); + } + }); + }; + + const handleSubmit = (data: BrevForTilbakekrevingFormData) => { + save(data, () => { + console.log('navigering til neste steg'); + }); + }; + + const onSeBrevClick = () => { + console.log('onSeBrevClick'); + }; + + const { isSaving } = useAutosaveOnChange(form.watch('brevtekst'), () => + autosave({ + sakId: props.sakId, + saksversjon: props.saksversjon, + behandlingId: props.tilbakekreving.id, + brevtekst: form.watch('brevtekst') ?? '', + }), + ); + + return ( + + {{ + left: ( +
+
+ ( +