Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend for visning av pdf #1790

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,745 changes: 4,063 additions & 1,682 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
]
},
"dependencies": {
"@amplitude/analytics-browser": "2.11.9",
"@navikt/ds-css": "^7.5.3",
"@navikt/ds-react": "^7.5.3",
"@navikt/ds-tokens": "^7.5.1",
Expand All @@ -50,7 +51,6 @@
"@types/react-dom": "^18.3.1",
"@types/react-modal": "^3.16.3",
"@types/react-router-dom": "^5.3.3",
"@amplitude/analytics-browser": "2.11.9",
"axios": "^1.7.7",
"be-pretty": "^1.1.3",
"body-parser": "^1.20.3",
Expand All @@ -62,6 +62,7 @@
"iso-datestring-validator": "^2.2.2",
"lodash": "^4.17.21",
"lodash.throttle": "^4.1.1",
"lucide-react": "^0.460.0",
"nav-faker": "^3.2.4",
"prop-types": "^15.8.1",
"react": "^18.3.1",
Expand All @@ -70,6 +71,8 @@
"react-dropzone": "^14.3.5",
"react-helmet": "^6.1.0",
"react-modal": "^3.16.1",
"react-pdf": "^9.1.1",
"react-query": "^3.39.3",
"react-router-dom": "^7.0.1",
"styled-components": "^6.1.13",
"use-debounce": "^10.0.4",
Expand Down
32 changes: 19 additions & 13 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import * as Sentry from '@sentry/browser';
import Environment from './Environment';
import SkolepengerApp from './skolepenger/SkolepengerApp';
import { createRoot } from 'react-dom/client';
import { PdfKvittering } from './overgangsstønad/Pdfkvittering';
import { QueryClient, QueryClientProvider } from 'react-query';

if (Environment().sentryUrl) {
Sentry.init({
Expand All @@ -36,20 +38,24 @@ if (container == null) {
throw new Error('Mangler container for appen');
} else {
const root = createRoot(container);
const queryClient = new QueryClient();
root.render(
<SpråkProvider>
<ContextProviders>
<Router basename={process.env.PUBLIC_URL}>
<ScrollToTop />
<Routes>
<Route path={'/arbeidssoker/*'} element={<ArbeidssøkerApp />} />
<Route path={'/barnetilsyn/*'} element={<BarnetilsynApp />} />
<Route path={'/skolepenger/*'} element={<SkolepengerApp />} />
<Route path={'*'} element={<App />} />
</Routes>
</Router>
</ContextProviders>
</SpråkProvider>
<QueryClientProvider client={queryClient}>
<SpråkProvider>
<ContextProviders>
<Router basename={process.env.PUBLIC_URL}>
<ScrollToTop />
<Routes>
<Route path={'/arbeidssoker/*'} element={<ArbeidssøkerApp />} />
<Route path={'/barnetilsyn/*'} element={<BarnetilsynApp />} />
<Route path={'/skolepenger/*'} element={<SkolepengerApp />} />
<Route path={'/pdf-kvittering/*'} element={<PdfKvittering />} />
<Route path={'*'} element={<App />} />
</Routes>
</Router>
</ContextProviders>
</SpråkProvider>
</QueryClientProvider>
);
}

Expand Down
5 changes: 5 additions & 0 deletions src/overgangsstønad/Pdfkvittering.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PdfKvitteringViser } from '../pdfKvittering/PdfKvitteringViser';

export const PdfKvittering = () => {
return <PdfKvitteringViser />;
};
92 changes: 92 additions & 0 deletions src/pdfKvittering/PdfKvitteringViser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useEffect, useState } from 'react';
import { pdfjs, Page as PdfPage, Document } from 'react-pdf';
import 'react-pdf/dist/Page/TextLayer.css';
import 'react-pdf/dist/Page/AnnotationLayer.css';
import '@navikt/ds-css';
import axios from 'axios';
import Environment from '../Environment';
import { HStack, VStack, Button, Page } from '@navikt/ds-react';
import { Box } from 'lucide-react';
import { CloudDownIcon } from '@navikt/aksel-icons';

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url
).toString();

const axiosConfig = {
withCredentials: true,
headers: {
'Content-Type': 'application/json',
accept: 'application/json',
},
};
export const hentPdfKvittering = async (): Promise<any> => {
const response = await axios.get(
`${Environment().apiProxyUrl}/api/soknadskvittering/a7943223-a851-4e43-a150-2a000d8af05d`,
axiosConfig
);

return response && response.data;
};

export function PdfKvitteringViser() {
const [pdfLenke, settPdfLenke] = useState<string | null>(null);
const [sidetall, settSidetall] = useState(1);
const [antallSider, settAntallSider] = useState(0);

useEffect(() => {
hentPdfKvittering().then((pdfKvittering) => {
const lenkeKilde = `data:application/pdf;base64,${pdfKvittering.pdf}`;
settPdfLenke(lenkeKilde);
});
}, []);

return (
<Page className="bg-blue-100">
{pdfLenke && (
<>
<HStack margin={'2'} gap={'10'}>
<Box className="mr-8 overflow-auto w-fit max-w-[60%]">
<Document
file={pdfLenke}
onLoadSuccess={({ numPages }) => settAntallSider(numPages)}
>
<PdfPage pageNumber={sidetall}></PdfPage>
</Document>
</Box>

<VStack gap={'3'}>
<HStack justify={'space-between'}>
<VStack>
<p>
Side {sidetall} av {antallSider}
</p>

<HStack gap={'4'}>
<Button
disabled={sidetall === 1}
onClick={() => settSidetall(sidetall - 1)}
>
Forrige
</Button>
f
<Button
disabled={sidetall === antallSider}
onClick={() => settSidetall(sidetall + 1)}
>
Neste
</Button>
<Button icon={<CloudDownIcon aria-hidden />}>
Last ned Pdf
</Button>
</HStack>
</VStack>
</HStack>
</VStack>
</HStack>
</>
)}
</Page>
);
}
73 changes: 73 additions & 0 deletions src/pdfKvittering/components/Standard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { Fragment, useState } from 'react';
import { HStack } from '@navikt/ds-react';
import { Check, X } from 'lucide-react';
import {
feiletRegelSammenligner as feiledeReglerSammenligner,
parseFeiletRegel,
} from '../utils/RegelUtils';
import { clsx } from 'clsx';

export type Standard = {
samsvarer: boolean;
feiletRegel?: string;
};

interface StandardProps {
standardType: string;
standard: Standard;
}

export function Standard({ standard, standardType }: StandardProps) {
const [erÅpen, settErÅpen] = useState(false);
const feiledeRegler = parseFeiletRegel(standard.feiletRegel);

return (
<HStack>
<div
className={clsx(
'p-2 rounded w-full mb-2',
standard && {
'bg-green-100': standard.samsvarer,
'bg-red-100': !standard.samsvarer,
}
)}
>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<div
onClick={() => settErÅpen(!erÅpen)}
className={clsx('flex justify-between', {
'cursor-pointer': !standard.samsvarer,
})}
>
<HStack>
{standard.samsvarer ? <Check /> : <X />}
{standardType}
</HStack>
<span>{standard.samsvarer ? 'Samsvarer' : 'Samsvarer ikke'}</span>
</div>
{erÅpen && !standard.samsvarer && (
<>
{feiledeRegler.length > 0 && (
<ol>
{feiledeRegler.sort(feiledeReglerSammenligner).map(
(regel, indeks) =>
regel && (
<Fragment key={`${indeks}${regel}`}>
<li className="text-gray-700 flex-col flex">
<strong>Ikke oppfylt regel:</strong>
<span>{regel}</span>
</li>
{indeks < feiledeRegler.length - 1 && (
<hr className="my-2 border-black" />
)}
</Fragment>
)
)}
</ol>
)}
</>
)}
</div>
</HStack>
);
}
42 changes: 42 additions & 0 deletions src/pdfKvittering/components/Standarder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { Standard } from './Standard';
import { VStack } from '@navikt/ds-react';

export type StandardTyper =
| 'ua1'
| 'ua2'
| '1a'
| '1b'
| '2a'
| '2b'
| '2u'
| '3a'
| '3b'
| '3u'
| '4'
| '4f'
| '4e';

export type Standarder = {
[key in StandardTyper]: Standard;
};

interface StandarderProps {
standarder: Standarder;
}

export function Standarder({ standarder }: StandarderProps) {
return (
<VStack>
{Object.entries(standarder).map(([standardType, standard]) => {
return (
<Standard
key={standardType}
standardType={standardType}
standard={standard}
/>
);
})}
</VStack>
);
}
54 changes: 54 additions & 0 deletions src/pdfKvittering/utils/RegelUtils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
function hentKlausulOgTestNummer(regel: string): [number[], number] {
const klausulTreff = regel.match(/clause=([\d.]+)/);
const testNummerTreff = regel.match(/testNumber=(\d+)/);

if (!klausulTreff?.[1] || !testNummerTreff?.[1]) {
throw new Error(`Ugyldig regelformat: ${regel}`);
}

const klausul = klausulTreff[1].split('.').map(Number);
const testNummer = parseInt(testNummerTreff[1], 10);

return [klausul, testNummer];
}

export function feiletRegelSammenligner(a: string, b: string): number {
const [klausulA, testNumberA] = hentKlausulOgTestNummer(a);
const [klausulB, testNumberB] = hentKlausulOgTestNummer(b);

if (klausulA.length !== klausulB.length) {
return klausulA.length - klausulB.length;
}

for (let i = 0; i < klausulA.length; i++) {
const elementA = klausulA?.[i];
const elementB = klausulB?.[i];
const typeA = typeof elementA;
const typeB = typeof elementB;

if (
elementA === undefined ||
elementB === undefined ||
typeA !== 'number' ||
typeB !== 'number'
) {
throw new Error(`Ugyldig elementtype: ${a} eller ${b} er ikke et tall`);
}

if (elementA !== elementB) {
return elementB - elementA;
}
}

return testNumberB - testNumberA;
}

export function parseFeiletRegel(feiletRegel: string | undefined): string[] {
if (!feiletRegel) {
return [];
}
return feiletRegel
.slice(1, -1)
.split(',')
.map((regel) => regel.trim());
}