diff --git a/package-lock.json b/package-lock.json
index 386357f8..d59342ea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,9 +13,9 @@
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
- "@mui/icons-material": "^6.2.1",
- "@mui/material": "^6.2.1",
- "@mui/material-nextjs": "^6.2.1",
+ "@mui/icons-material": "^6.4.0",
+ "@mui/material": "^6.4.0",
+ "@mui/material-nextjs": "^6.3.1",
"@opetushallitus/oph-design-system": "github:opetushallitus/oph-design-system#v0.1.8",
"@tanstack/react-query": "^5.62.8",
"@tanstack/react-query-devtools": "^5.62.8",
@@ -1319,9 +1319,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.2.1.tgz",
- "integrity": "sha512-U/8vS1+1XiHBnnRRESSG1gvr6JDHdPjrpnW6KEebkAQWBn6wrpbSF/XSZ8/vJIRXH5NyDmMHi4Ro5Q70//JKhA==",
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.0.tgz",
+ "integrity": "sha512-6u74wi+9zeNlukrCtYYET8Ed/n9AS27DiaXCZKAD3TRGFaqiyYSsQgN2disW83pI/cM1Q2lJY1JX4YfwvNtlNw==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -1329,9 +1329,9 @@
}
},
"node_modules/@mui/icons-material": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.2.1.tgz",
- "integrity": "sha512-bP0XtW+t5KFL+wjfQp2UctN/8CuWqF1qaxbYuCAsJhL+AzproM8gGOh2n8sNBcrjbVckzDNqaXqxdpn+OmoWug==",
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.0.tgz",
+ "integrity": "sha512-zF0Vqt8a+Zp2Oz8P+WvJflba6lLe3PhxIz1NNqn+n4A+wKLPbkeqY8ShmKjPyiCTg0RMbPrp993oUDl9xGsDlQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0"
@@ -1344,7 +1344,7 @@
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
- "@mui/material": "^6.2.1",
+ "@mui/material": "^6.4.0",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
@@ -1355,16 +1355,16 @@
}
},
"node_modules/@mui/material": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.2.1.tgz",
- "integrity": "sha512-7VlKGsRKsy1bOSOPaSNgpkzaL+0C7iWAVKd2KYyAvhR9fTLJtiAMpq+KuzgEh1so2mtvQERN0tZVIceWMiIesw==",
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.0.tgz",
+ "integrity": "sha512-hNIgwdM9U3DNmowZ8mU59oFmWoDKjc92FqQnQva3Pxh6xRKWtD2Ej7POUHMX8Dwr1OpcSUlT2+tEMeLb7WYsIg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
- "@mui/core-downloads-tracker": "^6.2.1",
- "@mui/system": "^6.2.1",
- "@mui/types": "^7.2.20",
- "@mui/utils": "^6.2.1",
+ "@mui/core-downloads-tracker": "^6.4.0",
+ "@mui/system": "^6.4.0",
+ "@mui/types": "^7.2.21",
+ "@mui/utils": "^6.4.0",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
@@ -1383,7 +1383,7 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
- "@mui/material-pigment-css": "^6.2.1",
+ "@mui/material-pigment-css": "^6.4.0",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1404,9 +1404,9 @@
}
},
"node_modules/@mui/material-nextjs": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/@mui/material-nextjs/-/material-nextjs-6.2.1.tgz",
- "integrity": "sha512-PiCsm5YVbWi+SgIAXvJidfX0m++Sri0aJiLe8cJZKnYoBCl7MT2mW/f73KmrNaFy1TmeXPKTg7EKu9f48o0eFg==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@mui/material-nextjs/-/material-nextjs-6.3.1.tgz",
+ "integrity": "sha512-14Y9wHdGsxI7u9XiMlpK5L6+MTsGo3Pod0EqwEde3jMx6dv63uqnMokhC1mzIJ3PjWtG8FwJkDsl57O9H6d+gQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0"
@@ -1445,13 +1445,13 @@
"license": "MIT"
},
"node_modules/@mui/private-theming": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.2.1.tgz",
- "integrity": "sha512-u1y0gpcfrRRxCcIdVeU5eIvkinA82Q8ft178WUNYuoFQrsOrXdlBdZlRVi+eYuUFp1iXI55Cud7sMZZtETix5Q==",
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.0.tgz",
+ "integrity": "sha512-rNHci8MP6NOdEWAfZ/RBMO5Rhtp1T6fUDMSmingg9F1T6wiUeodIQ+NuTHh2/pMoUSeP9GdHdgMhMmfsXxOMuw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
- "@mui/utils": "^6.2.1",
+ "@mui/utils": "^6.4.0",
"prop-types": "^15.8.1"
},
"engines": {
@@ -1472,9 +1472,9 @@
}
},
"node_modules/@mui/styled-engine": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.2.1.tgz",
- "integrity": "sha512-6R3OgYw6zgCZWFYYMfxDqpGfJA78mUTOIlUDmmJlr60ogVNCrM87X0pqx5TbZ2OwUyxlJxN9qFgRr+J9H6cOBg==",
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.0.tgz",
+ "integrity": "sha512-ek/ZrDujrger12P6o4luQIfRd2IziH7jQod2WMbLqGE03Iy0zUwYmckRTVhRQTLPNccpD8KXGcALJF+uaUQlbg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
@@ -1506,16 +1506,16 @@
}
},
"node_modules/@mui/system": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.2.1.tgz",
- "integrity": "sha512-0lc8CbBP4WAAF+SmGMFJI9bpIyQvW3zvwIDzLsb26FIB/4Z0pO7qGe8mkAl0RM63Vb37899qxnThhHKgAAdy6w==",
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.0.tgz",
+ "integrity": "sha512-wTDyfRlaZCo2sW2IuOsrjeE5dl0Usrs6J7DxE3GwNCVFqS5wMplM2YeNiV3DO7s53RfCqbho+gJY6xaB9KThUA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
- "@mui/private-theming": "^6.2.1",
- "@mui/styled-engine": "^6.2.1",
- "@mui/types": "^7.2.20",
- "@mui/utils": "^6.2.1",
+ "@mui/private-theming": "^6.4.0",
+ "@mui/styled-engine": "^6.4.0",
+ "@mui/types": "^7.2.21",
+ "@mui/utils": "^6.4.0",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
@@ -1546,9 +1546,9 @@
}
},
"node_modules/@mui/types": {
- "version": "7.2.20",
- "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.20.tgz",
- "integrity": "sha512-straFHD7L8v05l/N5vcWk+y7eL9JF0C2mtph/y4BPm3gn2Eh61dDwDB65pa8DLss3WJfDXYC7Kx5yjP0EmXpgw==",
+ "version": "7.2.21",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz",
+ "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==",
"license": "MIT",
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1560,13 +1560,13 @@
}
},
"node_modules/@mui/utils": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.2.1.tgz",
- "integrity": "sha512-ubLqGIMhKUH2TF/Um+wRzYXgAooQw35th+DPemGrTpgrZHpOgcnUDIDbwsk1e8iQiuJ3mV/ErTtcQrecmlj5cg==",
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.0.tgz",
+ "integrity": "sha512-woOTATWNsTNR3YBh2Ixkj3l5RaxSiGoC9G8gOpYoFw1mZM77LWJeuMHFax7iIW4ahK0Cr35TF9DKtrafJmOmNQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.0",
- "@mui/types": "^7.2.20",
+ "@mui/types": "^7.2.21",
"@types/prop-types": "^15.7.14",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
diff --git a/package.json b/package.json
index 764cc958..f19d8b23 100644
--- a/package.json
+++ b/package.json
@@ -30,9 +30,9 @@
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
- "@mui/icons-material": "^6.2.1",
- "@mui/material": "^6.2.1",
- "@mui/material-nextjs": "^6.2.1",
+ "@mui/icons-material": "^6.4.0",
+ "@mui/material": "^6.4.0",
+ "@mui/material-nextjs": "^6.3.1",
"@opetushallitus/oph-design-system": "github:opetushallitus/oph-design-system#v0.1.8",
"@tanstack/react-query": "^5.62.8",
"@tanstack/react-query-devtools": "^5.62.8",
diff --git a/src/app/components/action-bar.tsx b/src/app/components/action-bar.tsx
index 0ec2f7c7..2a93f6bf 100644
--- a/src/app/components/action-bar.tsx
+++ b/src/app/components/action-bar.tsx
@@ -18,7 +18,7 @@ export const Button = withDefaultProps(
},
})),
{ variant: 'text' },
-);
+) as typeof OphButton;
export const Container = styled(Box)(({ theme }) => ({
display: 'flex',
diff --git a/src/app/components/download-button.tsx b/src/app/components/download-button.tsx
deleted file mode 100644
index 0a401020..00000000
--- a/src/app/components/download-button.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { ButtonProps } from '@mui/material';
-import { UseMutationResult } from '@tanstack/react-query';
-import { SpinnerIcon } from './spinner-icon';
-import { OphButton } from '@opetushallitus/oph-design-system';
-import { FileDownloadOutlined } from '@mui/icons-material';
-
-export const DownloadButton = ({
- mutation,
- startIcon = ,
- disabled,
- children,
- spinner = ,
- Component = OphButton,
-}: Pick & {
- mutation: UseMutationResult;
- spinner?: React.ReactNode;
- Component?: React.ComponentType<
- Pick
- >;
-}) => {
- const { isPending, mutate } = mutation;
- return (
- mutate()}
- >
- {children}
-
- );
-};
diff --git a/src/app/components/file-download-button.tsx b/src/app/components/file-download-button.tsx
new file mode 100644
index 00000000..cf7cea00
--- /dev/null
+++ b/src/app/components/file-download-button.tsx
@@ -0,0 +1,50 @@
+import { FileResult } from '@/app/lib/http-client';
+import useToaster from '@/app/hooks/useToaster';
+import { useFileDownloadMutation } from '../hooks/useFileDownloadMutation';
+import { FileDownloadOutlined } from '@mui/icons-material';
+import { OphButton } from '@opetushallitus/oph-design-system';
+import { ButtonProps } from '@mui/material';
+
+type FileDownloadProps = {
+ component?: typeof OphButton;
+ icon?: React.ReactNode;
+ getFile: () => Promise;
+ children: React.ReactNode;
+ defaultFileName: string;
+ errorKey: string;
+ errorMessage: string;
+} & Omit;
+
+export function FileDownloadButton({
+ defaultFileName,
+ children,
+ getFile,
+ errorKey,
+ errorMessage,
+ startIcon,
+ ...props
+}: FileDownloadProps) {
+ const { addToast } = useToaster();
+ const mutation = useFileDownloadMutation({
+ onError: () => {
+ addToast({
+ key: errorKey,
+ message: errorMessage,
+ type: 'error',
+ });
+ },
+ getFile,
+ defaultFileName,
+ });
+
+ return (
+ }
+ loading={mutation.isPending}
+ onClick={() => mutation.mutate()}
+ {...props}
+ >
+ {children}
+
+ );
+}
diff --git a/src/app/components/localized-select.tsx b/src/app/components/localized-select.tsx
index 3194a6b4..428cefc6 100644
--- a/src/app/components/localized-select.tsx
+++ b/src/app/components/localized-select.tsx
@@ -9,6 +9,7 @@ export const LocalizedSelect = (
return (
diff --git a/src/app/haku/[oid]/hakukohde/[hakukohde]/harkinnanvaraiset/components/harkinnanvaraiset-action-bar.tsx b/src/app/haku/[oid]/hakukohde/[hakukohde]/harkinnanvaraiset/components/harkinnanvaraiset-action-bar.tsx
index aa2ff4c4..a2f9e6a8 100644
--- a/src/app/haku/[oid]/hakukohde/[hakukohde]/harkinnanvaraiset/components/harkinnanvaraiset-action-bar.tsx
+++ b/src/app/haku/[oid]/hakukohde/[hakukohde]/harkinnanvaraiset/components/harkinnanvaraiset-action-bar.tsx
@@ -7,34 +7,10 @@ import {
NoteOutlined,
} from '@mui/icons-material';
import { ActionBar } from '@/app/components/action-bar';
-import { useMutation } from '@tanstack/react-query';
-import useToaster from '@/app/hooks/useToaster';
-import { DownloadButton } from '@/app/components/download-button';
import { getOsoitetarratHakemuksille } from '@/app/lib/valintalaskentakoostepalvelu';
-import { downloadBlob } from '@/app/lib/common';
import { HarkinnanvaraisetTilatByHakemusOids } from '@/app/lib/types/harkinnanvaraiset-types';
-
-const useOsoitetarratMutation = ({ selection }: { selection: Set }) => {
- const { addToast } = useToaster();
-
- return useMutation({
- mutationFn: async () => {
- const { fileName, blob } = await getOsoitetarratHakemuksille({
- tag: 'harkinnanvaraiset',
- hakemusOids: Array.from(selection),
- });
- downloadBlob(fileName ?? 'osoitetarrat.pdf', blob);
- },
- onError: (e) => {
- addToast({
- key: 'get-osoitetarrat',
- message: 'harkinnanvaraiset.virhe-osoitetarrat',
- type: 'error',
- });
- console.error(e);
- },
- });
-};
+import { FileDownloadButton } from '@/app/components/file-download-button';
+import { useCallback } from 'react';
const HyvaksyValitutButton = ({
selection,
@@ -71,19 +47,27 @@ const OsoitetarratDownloadButton = ({
}) => {
const { t } = useTranslation();
- const osoitetarratMutation = useOsoitetarratMutation({
- selection,
- });
+ const getFile = useCallback(
+ () =>
+ getOsoitetarratHakemuksille({
+ tag: 'harkinnanvaraiset',
+ hakemusOids: Array.from(selection),
+ }),
+ [selection],
+ );
return (
- }
+ defaultFileName="osoitetarrat.pdf"
+ getFile={getFile}
+ errorKey="get-osoitetarrat-error"
+ errorMessage="harkinnanvaraiset.virhe-osoitetarrat"
>
{t('harkinnanvaraiset.muodosta-osoitetarrat')}
-
+
);
};
diff --git a/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-actions.tsx b/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-actions.tsx
index 37868ad5..99cdd858 100644
--- a/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-actions.tsx
+++ b/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-actions.tsx
@@ -1,8 +1,8 @@
import { useTranslations } from '@/app/hooks/useTranslations';
import { CircularProgress, Stack } from '@mui/material';
import { OphButton } from '@opetushallitus/oph-design-system';
-import { ExcelUploadButton } from './pistesyotto-excel-upload-button';
-import { ExcelDownloadButton } from './pistesyotto-excel-download-button';
+import { PistesyottoExcelUploadButton } from './pistesyotto-excel-upload-button';
+import { PistesyottoExcelDownloadButton } from './pistesyotto-excel-download-button';
export const PisteSyottoActions = ({
hakuOid,
@@ -29,8 +29,8 @@ export const PisteSyottoActions = ({
{isUpdating && (
)}
-
-
+
+
);
};
diff --git a/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-excel-download-button.tsx b/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-excel-download-button.tsx
index fc77cfd2..83836465 100644
--- a/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-excel-download-button.tsx
+++ b/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-excel-download-button.tsx
@@ -1,39 +1,8 @@
-import { DownloadButton } from '@/app/components/download-button';
-import useToaster from '@/app/hooks/useToaster';
+import { FileDownloadButton } from '@/app/components/file-download-button';
import { useTranslations } from '@/app/hooks/useTranslations';
-import { downloadBlob } from '@/app/lib/common';
import { getPistesyottoExcel } from '@/app/lib/valintalaskentakoostepalvelu';
-import { useMutation } from '@tanstack/react-query';
-const useExcelDownloadMutation = ({
- hakuOid,
- hakukohdeOid,
-}: {
- hakuOid: string;
- hakukohdeOid: string;
-}) => {
- const { addToast } = useToaster();
-
- return useMutation({
- mutationFn: async () => {
- const { fileName, blob } = await getPistesyottoExcel({
- hakuOid,
- hakukohdeOid,
- });
- downloadBlob(fileName ?? 'pistesyotto.xls', blob);
- },
- onError: (e) => {
- addToast({
- key: 'get-pistesyotto-excel',
- message: 'pistesyotto.virhe-vie-taulukkolaskentaan',
- type: 'error',
- });
- console.error(e);
- },
- });
-};
-
-export const ExcelDownloadButton = ({
+export const PistesyottoExcelDownloadButton = ({
hakuOid,
hakukohdeOid,
}: {
@@ -42,14 +11,14 @@ export const ExcelDownloadButton = ({
}) => {
const { t } = useTranslations();
- const excelMutation = useExcelDownloadMutation({
- hakuOid,
- hakukohdeOid,
- });
-
return (
-
+ getPistesyottoExcel({ hakuOid, hakukohdeOid })}
+ >
{t('yleinen.vie-taulukkolaskentaan')}
-
+
);
};
diff --git a/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-excel-upload-button.tsx b/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-excel-upload-button.tsx
index 12d5e6eb..ca40cc00 100644
--- a/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-excel-upload-button.tsx
+++ b/src/app/haku/[oid]/hakukohde/[hakukohde]/pistesyotto/components/pistesyotto-excel-upload-button.tsx
@@ -139,7 +139,7 @@ const FileSelector = forwardRef(
},
);
-export const ExcelUploadButton = ({
+export const PistesyottoExcelUploadButton = ({
hakuOid,
hakukohdeOid,
}: {
diff --git a/src/app/haku/[oid]/hakukohde/[hakukohde]/valintakoekutsut/components/valintakoekutsut-action-bar.tsx b/src/app/haku/[oid]/hakukohde/[hakukohde]/valintakoekutsut/components/valintakoekutsut-action-bar.tsx
index 330c0d52..b05ab619 100644
--- a/src/app/haku/[oid]/hakukohde/[hakukohde]/valintakoekutsut/components/valintakoekutsut-action-bar.tsx
+++ b/src/app/haku/[oid]/hakukohde/[hakukohde]/valintakoekutsut/components/valintakoekutsut-action-bar.tsx
@@ -7,43 +7,8 @@ import {
GetValintakoeExcelParams,
getValintakoeOsoitetarrat,
} from '@/app/lib/valintalaskentakoostepalvelu';
-import { downloadBlob } from '@/app/lib/common';
-import { useMutation } from '@tanstack/react-query';
-import useToaster from '@/app/hooks/useToaster';
-import { DownloadButton } from '@/app/components/download-button';
-import { ValintakoekutsutDownloadProps } from '@/app/lib/types/valintakoekutsut-types';
import { ValintakoekutsutExcelDownloadButton } from './valintakoekutsut-excel-download-button';
-
-const useOsoitetarratMutation = ({
- hakuOid,
- hakukohdeOid,
- valintakoeTunniste,
- selection,
-}: Omit & {
- valintakoeTunniste: string;
-}) => {
- const { addToast } = useToaster();
-
- return useMutation({
- mutationFn: async () => {
- const { fileName, blob } = await getValintakoeOsoitetarrat({
- hakuOid,
- hakukohdeOid,
- valintakoeTunniste,
- hakemusOids: selection && Array.from(selection),
- });
- downloadBlob(fileName ?? 'osoitetarrat.pdf', blob);
- },
- onError: (e) => {
- addToast({
- key: 'get-osoitetarrat',
- message: 'valintakoekutsut.virhe-osoitetarrat',
- type: 'error',
- });
- console.error(e);
- },
- });
-};
+import { FileDownloadButton } from '@/app/components/file-download-button';
const OsoitetarratDownloadButton = ({
hakuOid,
@@ -58,22 +23,25 @@ const OsoitetarratDownloadButton = ({
}) => {
const { t } = useTranslation();
- const osoitetarratMutation = useOsoitetarratMutation({
- hakuOid,
- hakukohdeOid,
- valintakoeTunniste,
- selection,
- });
-
return (
- }
+ getFile={() =>
+ getValintakoeOsoitetarrat({
+ hakuOid,
+ hakukohdeOid,
+ valintakoeTunniste,
+ hakemusOids: Array.from(selection),
+ })
+ }
+ errorKey="get-osoitetarrat-error"
+ errorMessage="valintakoekutsut.virhe-osoitetarrat"
+ defaultFileName="osoitetarrat.pdf"
>
{t('valintakoekutsut.muodosta-osoitetarrat')}
-
+
);
};
@@ -101,7 +69,7 @@ export const ValintakoekutsutActionBar = ({
{
- const { addToast } = useToaster();
-
- return useMutation({
- mutationFn: async () => {
- const { fileName, blob } = await getValintakoeExcel({
- hakuOid,
- hakukohdeOid,
- valintakoeTunniste,
- hakemusOids: selection && Array.from(selection),
- });
- downloadBlob(fileName ?? 'valintakoekutsut.xls', blob);
- },
- onError: (e) => {
- addToast({
- key: 'get-valintakoe-excel',
- message: 'valintakoekutsut.virhe-vie-taulukkolaskentaan',
- type: 'error',
- });
- console.error(e);
- },
- });
-};
+import { OphButton } from '@opetushallitus/oph-design-system';
export const ValintakoekutsutExcelDownloadButton = ({
hakuOid,
hakukohdeOid,
valintakoeTunniste,
selection,
- Component,
+ component = OphButton,
}: {
hakuOid: string;
hakukohdeOid: string;
valintakoeTunniste: Array;
selection?: Set;
- Component?: React.ComponentType;
+ component?: typeof OphButton;
}) => {
const { t } = useTranslations();
- const excelMutation = useExcelDownloadMutation({
- hakuOid,
- hakukohdeOid,
- valintakoeTunniste,
- selection,
- });
-
return (
-
+ getValintakoeExcel({
+ hakuOid,
+ hakukohdeOid,
+ valintakoeTunniste,
+ hakemusOids: selection && Array.from(selection),
+ })
+ }
>
{t('yleinen.vie-taulukkolaskentaan')}
-
+
);
};
diff --git a/src/app/haku/[oid]/hakukohde/[hakukohde]/valintalaskennan-tulokset/page.tsx b/src/app/haku/[oid]/hakukohde/[hakukohde]/valintalaskennan-tulokset/page.tsx
index 0c81edd4..84e967bf 100644
--- a/src/app/haku/[oid]/hakukohde/[hakukohde]/valintalaskennan-tulokset/page.tsx
+++ b/src/app/haku/[oid]/hakukohde/[hakukohde]/valintalaskennan-tulokset/page.tsx
@@ -6,57 +6,37 @@ import { Box } from '@mui/material';
import { useTranslations } from '@/app/hooks/useTranslations';
import { useLasketutValinnanVaiheet } from '@/app/hooks/useLasketutValinnanVaiheet';
import { PageSizeSelector } from '@/app/components/table/page-size-selector';
-import React, { use } from 'react';
+import { use } from 'react';
import { ValintatapajonoContent } from './components/valintatapajono-content';
import { useJonosijatSearchParams } from '@/app/hooks/useJonosijatSearch';
import { FullClientSpinner } from '@/app/components/client-spinner';
-import { downloadBlob, isEmpty } from '@/app/lib/common';
-import { DownloadButton } from '@/app/components/download-button';
-import useToaster from '@/app/hooks/useToaster';
-import { useMutation } from '@tanstack/react-query';
+import { isEmpty } from '@/app/lib/common';
import { getValintalaskennanTulosExcel } from '@/app/lib/valintalaskentakoostepalvelu';
import { NoResults } from '@/app/components/no-results';
import { SearchInput } from '@/app/components/search-input';
+import { FileDownloadButton } from '@/app/components/file-download-button';
type LasketutValinnanvaiheetParams = {
hakuOid: string;
hakukohdeOid: string;
};
-const useExcelDownloadMutation = ({
+const LaskennanTuloksetExcelDownloadButton = ({
hakukohdeOid,
}: {
hakukohdeOid: string;
}) => {
- const { addToast } = useToaster();
-
- return useMutation({
- mutationFn: async () => {
- const { fileName, blob } = await getValintalaskennanTulosExcel({
- hakukohdeOid,
- });
- downloadBlob(fileName ?? 'valintalaskennan-tulokset.xls', blob);
- },
- onError: (e) => {
- addToast({
- key: 'get-valintakoe-excel',
- message:
- 'valintalaskennan-tulokset.virhe-vie-kaikki-taulukkolaskentaan',
- type: 'error',
- });
- console.error(e);
- },
- });
-};
-
-const ExcelDownloadButton = ({ hakukohdeOid }: { hakukohdeOid: string }) => {
- const mutation = useExcelDownloadMutation({ hakukohdeOid });
const { t } = useTranslations();
return (
-
+ getValintalaskennanTulosExcel({ hakukohdeOid })}
+ >
{t('valintalaskennan-tulokset.vie-kaikki-taulukkolaskentaan')}
-
+
);
};
@@ -105,7 +85,7 @@ const LasketutValinnanVaiheetContent = ({
setSearchPhrase={setSearchPhrase}
name="valintalaskennan-tulokset-search"
/>
-
+
diff --git a/src/app/haku/[oid]/henkilo/[hakemusOid]/components/valinnan-tilat-edit-modal.tsx b/src/app/haku/[oid]/henkilo/[hakemusOid]/components/valinnan-tilat-edit-modal.tsx
index 4f815902..0e2a242d 100644
--- a/src/app/haku/[oid]/henkilo/[hakemusOid]/components/valinnan-tilat-edit-modal.tsx
+++ b/src/app/haku/[oid]/henkilo/[hakemusOid]/components/valinnan-tilat-edit-modal.tsx
@@ -5,7 +5,7 @@ import {
} from '@/app/components/global-modal';
import { useTranslations } from '@/app/hooks/useTranslations';
import { Stack } from '@mui/material';
-import { OphButton, OphSelect } from '@opetushallitus/oph-design-system';
+import { OphButton } from '@opetushallitus/oph-design-system';
import { InlineFormControl, PaddedLabel } from './inline-form-control';
import useToaster from '@/app/hooks/useToaster';
import {
@@ -29,6 +29,7 @@ import { ValinnanTulosUpdateErrorResult } from '@/app/lib/types/valinta-tulos-ty
import { HttpClientResponse } from '@/app/lib/http-client';
import { EditModalDialog } from './edit-modal-dialog';
import { ValinnanTulosLisatiedoilla } from '../lib/henkilo-page-types';
+import { LocalizedSelect } from '@/app/components/localized-select';
const ModalActions = ({
onClose,
@@ -204,7 +205,7 @@ export const ValinnanTilatEditModal = createModal<{
{t('henkilo.taulukko.vastaanoton-tila')}
}
renderInput={({ labelId }) => (
-
}
renderInput={({ labelId }) => (
-
}
renderInput={({ labelId }) => (
- {t('henkilo.taulukko.jarjestyskriteeri')}
}
renderInput={({ labelId }) => (
- void;
+ getFile: () => Promise;
+ defaultFileName: string;
+}) {
+ return useMutation({
+ mutationFn: async () => {
+ const { fileName, blob } = await getFile();
+ downloadBlob(fileName ?? defaultFileName, blob);
+ },
+ onError: (e) => {
+ onError(e);
+ console.error(e);
+ },
+ });
+}
diff --git a/src/app/lib/http-client.ts b/src/app/lib/http-client.ts
index ae2e933e..3387e5e3 100644
--- a/src/app/lib/http-client.ts
+++ b/src/app/lib/http-client.ts
@@ -9,6 +9,23 @@ export type HttpClientResponse = {
data: D;
};
+const getContentFilename = (headers: Headers) => {
+ const contentDisposition = headers.get('content-disposition');
+ return contentDisposition?.match(/ filename="(.*)"$/)?.[1];
+};
+
+export type FileResult = {
+ fileName?: string;
+ blob: Blob;
+};
+
+export const createFileResult = async (
+ response: HttpClientResponse,
+): Promise => ({
+ fileName: getContentFilename(response.headers),
+ blob: response.data,
+});
+
const doFetch = async (request: Request) => {
try {
const response = await fetch(request);
diff --git a/src/app/lib/theme.tsx b/src/app/lib/theme.tsx
index 26850b9f..dae43b3e 100644
--- a/src/app/lib/theme.tsx
+++ b/src/app/lib/theme.tsx
@@ -91,5 +91,10 @@ export const THEME_OVERRIDES: ThemeOptions = {
}),
},
},
+ MuiButton: {
+ defaultProps: {
+ loadingPosition: 'start',
+ },
+ },
},
};
diff --git a/src/app/lib/valintalaskentakoostepalvelu.ts b/src/app/lib/valintalaskentakoostepalvelu.ts
index 6ca55946..1fc3632e 100644
--- a/src/app/lib/valintalaskentakoostepalvelu.ts
+++ b/src/app/lib/valintalaskentakoostepalvelu.ts
@@ -1,5 +1,5 @@
import { configuration } from './configuration';
-import { abortableClient, client, HttpClientResponse } from './http-client';
+import { abortableClient, client, createFileResult } from './http-client';
import { HenkilonValintaTulos } from './types/sijoittelu-types';
import {
HakemuksenPistetiedot,
@@ -30,6 +30,7 @@ import { HarkinnanvaraisuudenSyy } from './types/harkinnanvaraiset-types';
import { ValintakoeAvaimet } from './types/valintaperusteet-types';
import { Hakukohde } from './types/kouta-types';
import { getOpetuskieliCode } from './kouta';
+import { AssertionError } from 'assert';
export const getHakukohteenValintatuloksetIlmanHakijanTilaa = async (
hakuOid: string,
@@ -259,30 +260,44 @@ export type GetValintakoeExcelParams = {
valintakoeTunniste: Array;
};
-const getContentFilename = (headers: Headers) => {
- const contentDisposition = headers.get('content-disposition');
- return contentDisposition?.match(/ filename="(.*)"$/)?.[1];
-};
+const pollDocumentProcess = async (processId: string) => {
+ let pollTimes = 10;
-const createFileResult = async (response: HttpClientResponse) => ({
- fileName: getContentFilename(response.headers),
- blob: response.data,
-});
+ while (pollTimes) {
+ const processRes = await client.get<{
+ dokumenttiId: string;
+ kasittelyssa: boolean;
+ keskeytetty: boolean;
+ kokonaistyo: {
+ valmis: boolean;
+ };
+ poikkeukset: Array<{
+ viesti: string;
+ }>;
+ }>(configuration.dokumenttiProsessiUrl({ id: processId }));
+ pollTimes -= 1;
+
+ const { data } = processRes;
+
+ if (data.kokonaistyo?.valmis || data.keskeytetty) {
+ return data;
+ } else if (pollTimes === 0) {
+ throw new OphApiError(
+ processRes,
+ 'Dokumentin prosessointi aikakatkaistiin',
+ );
+ }
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ }
+ throw new AssertionError({
+ message: 'Dokumentin prosessoinnin pollaus päättyi ilman tulosta!',
+ });
+};
const downloadProcessDocument = async (processId: string) => {
- const processRes = await client.get<{
- dokumenttiId: string;
- kasittelyssa: boolean;
- keskeytetty: boolean;
- kokonaistyo: {
- valmis: boolean;
- };
- poikkeukset: Array<{
- viesti: string;
- }>;
- }>(configuration.dokumenttiProsessiUrl({ id: processId }));
+ const data = await pollDocumentProcess(processId);
- const { dokumenttiId, poikkeukset } = processRes.data;
+ const { dokumenttiId, poikkeukset } = data;
if (!isEmpty(poikkeukset)) {
const errorMessages = poikkeukset.map(prop('viesti')).join('\n');
@@ -292,6 +307,7 @@ const downloadProcessDocument = async (processId: string) => {
const documentRes = await client.get(
configuration.lataaDokumenttiUrl({ dokumenttiId }),
);
+
return createFileResult(documentRes);
};
diff --git a/tests/e2e/pistesyotto.spec.ts b/tests/e2e/pistesyotto.spec.ts
index 1a2a5029..3892a7d3 100644
--- a/tests/e2e/pistesyotto.spec.ts
+++ b/tests/e2e/pistesyotto.spec.ts
@@ -202,7 +202,7 @@ test.describe('Excel export', () => {
),
async (route) => {
await route.fulfill({
- json: { dokumenttiId: 'doc_id' },
+ json: { dokumenttiId: 'doc_id', kokonaistyo: { valmis: true } },
});
},
);
diff --git a/tests/e2e/playwright-utils.ts b/tests/e2e/playwright-utils.ts
index 657df78e..ea78aa91 100644
--- a/tests/e2e/playwright-utils.ts
+++ b/tests/e2e/playwright-utils.ts
@@ -1,7 +1,6 @@
import AxeBuilder from '@axe-core/playwright';
import { Locator, Page, Route, expect } from '@playwright/test';
import path from 'path';
-import cssEscape from 'css.escape';
export const expectPageAccessibilityOk = async (page: Page) => {
const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
@@ -78,11 +77,11 @@ export async function selectOption(
const combobox = (within ?? page).getByRole('combobox', {
name: new RegExp(`^${name}`),
});
- const contentId = await combobox.getAttribute('aria-controls');
+
await combobox.click();
- const contentIdSelector = contentId ? `#${cssEscape(contentId)}` : '';
- // Selectin listbox rendataan portalilla juuritasolle
- const listbox = page.locator(contentIdSelector);
+
+ // Selectin listbox rendataan juuritasolle
+ const listbox = page.locator('#select-menu').getByRole('listbox');
await listbox
.getByRole('option', { name: expectedOption, exact: true })