diff --git a/cdk/bin/viestinvalityspalvelu.ts b/cdk/bin/viestinvalityspalvelu.ts index 4f0cef14..7bb566cd 100644 --- a/cdk/bin/viestinvalityspalvelu.ts +++ b/cdk/bin/viestinvalityspalvelu.ts @@ -5,7 +5,7 @@ import { SovellusStack } from '../lib/sovellus-stack'; import {PersistenssiStack} from "../lib/persistenssi-stack"; import {LoadtestStack} from "../lib/loadtest-stack"; import {MigraatioStack} from "../lib/migraatio-stack"; -import { AwsSolutionsChecks, NagSuppressions } from 'cdk-nag'; +import { AwsSolutionsChecks } from 'cdk-nag'; const app = new cdk.App(); const environmentName = app.node.tryGetContext("environment"); diff --git a/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/model/LahetyksetParamValidator.scala b/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/model/LahetyksetParamValidator.scala index af6122e0..b2571e39 100644 --- a/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/model/LahetyksetParamValidator.scala +++ b/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/model/LahetyksetParamValidator.scala @@ -17,7 +17,8 @@ case class LahetyksetParams(alkaen: Optional[String], enintaan: Optional[String], vastaanottajanEmail: Optional[String], organisaatio: Optional[String], - viesti: Optional[String]) + viesti: Optional[String], + palvelu: Optional[String]) object LahetyksetParamValidator { def validateAlkaenUUID(alkaen: Optional[String]): Set[String] = @@ -69,7 +70,7 @@ object LahetyksetParamValidator { .flatMap(virheet => if (hakusanaParam.isPresent && validatedHakusana.isEmpty) Left(virheet.incl(HAKUSANA_INVALID)) else Right(virheet)) .fold(l => l, r => r) - + def validateVastaanottajatParams(params: VastaanottajatParams): Seq[String] = Seq( validateLahetysTunniste(params.lahetysTunniste), @@ -86,6 +87,7 @@ object LahetyksetParamValidator { validateEnintaan(params.enintaan, LAHETYKSET_ENINTAAN_MIN, LAHETYKSET_ENINTAAN_MAX, LAHETYKSET_ENINTAAN_INVALID), validateEmailParam(params.vastaanottajanEmail, VASTAANOTTAJA_INVALID), validateOrganisaatio(params.organisaatio), - validateHakusanaParam(params.viesti) + validateHakusanaParam(params.viesti), + validateHakusanaParam(params.palvelu) ).flatten } diff --git a/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/resource/LahetysResource.scala b/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/resource/LahetysResource.scala index b6d8e334..0d1533ab 100644 --- a/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/resource/LahetysResource.scala +++ b/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/resource/LahetysResource.scala @@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory import org.springframework.http.{HttpStatus, MediaType, ResponseEntity} import org.springframework.web.bind.annotation.* import org.springframework.web.context.request.{RequestContextHolder, ServletRequestAttributes} +import upickle.default.* import java.util import java.util.{Optional, UUID} @@ -50,8 +51,8 @@ class LahetysResource { @RequestParam(name = VASTAANOTTAJA_PARAM_NAME, required = false) vastaanottajanEmail: Optional[String], @RequestParam(name = ORGANISAATIO_PARAM_NAME, required = false) organisaatio: Optional[String], @RequestParam(name = VIESTI_SISALTO_PARAM_NAME, required = false) viesti: Optional[String], + @RequestParam(name = PALVELU_PARAM_NAME, required = false) palvelu: Optional[String], request: HttpServletRequest): ResponseEntity[PalautaLahetyksetResponse] = - LOG.warn(s"Haetaan lähetyksiä parametreilla vastaanottaja: ${vastaanottajanEmail.toScala.getOrElse("")} viesti: ${viesti.toScala.getOrElse("")}") val securityOperaatiot = new SecurityOperaatiot val kantaOperaatiot = new KantaOperaatiot(DbUtil.database) try @@ -63,7 +64,7 @@ class LahetysResource { else Right(None)) .flatMap(_ => - val virheet = LahetyksetParamValidator.validateLahetyksetParams(LahetyksetParams(alkaen, enintaan, vastaanottajanEmail, organisaatio, viesti)) + val virheet = LahetyksetParamValidator.validateLahetyksetParams(LahetyksetParams(alkaen, enintaan, vastaanottajanEmail, organisaatio, viesti, palvelu)) if (!virheet.isEmpty) Left(ResponseEntity.status(HttpStatus.BAD_REQUEST).body(PalautaLahetyksetFailureResponse(virheet.asJava))) else @@ -77,7 +78,8 @@ class LahetysResource { alkaen = ParametriUtil.asUUID(alkaen), enintaan = ParametriUtil.asInt(enintaan).getOrElse(65535), vastaanottajaHakuLauseke = vastaanottajanEmail.toScala, - sisaltoHakuLauseke = viesti.toScala) // samalla hakusanalla haetaan otsikosta ja sisällöstä + sisaltoHakuLauseke = viesti.toScala, + lahettavaPalveluHakuLauseke = palvelu.toScala) if (lahetykset.isEmpty) // on ok tilanne että haku ei palauta tuloksia Left(ResponseEntity.status(HttpStatus.OK).body(PalautaLahetyksetSuccessResponse(Seq.empty.asJava, Optional.empty))) @@ -396,6 +398,28 @@ class LahetysResource { LOG.error("Vastaanottajien lukeminen epäonnistui", e) ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(VastaanottajatFailureResponse(Seq(RaportointiAPIConstants.VASTAANOTTAJAT_LUKEMINEN_EPAONNISTUI).asJava))) + @GetMapping(path = Array(RaportointiAPIConstants.PALVELUT_PATH), produces = Array(MediaType.APPLICATION_JSON_VALUE)) + @Operation( + summary = "Palauttaa listan lähetyksiä lähettäviä palveluja", + description = "Palauttaa lähettävien palvelujen listauksen käyttöliittymän hakutoimintoa varten", + responses = Array( + new ApiResponse(responseCode = "200", description = "Palauttaa listan palvelunimiä"), + )) + def getLahettavatPalvelut() = { + LOG.info("Haetaan lähettävät palvelut") + val kantaOperaatiot = new KantaOperaatiot(DbUtil.database) + try + // suodatetaan pois swagger-esimerkkirivin palvelu + val palvelut = kantaOperaatiot.getLahettavatPalvelut().filterNot(p => p.equals("Esimerkkipalvelu")) + LOG.info(s"Löytyi ${palvelut.size} palvelua") + ResponseEntity.status(HttpStatus.OK).body(write[List[String]](palvelut)) + catch + case e: Exception => + LOG.error("Lähettävien palvelujen haku epäonnistui", e) + ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(write(Map("message" -> e.getMessage))) + } + + def organisaatiorajaus(organisaatio: Optional[String], kayttajanOikeudet: Set[Kayttooikeus], organisaatioClient: OrganisaatioService): Set[Kayttooikeus] = if (organisaatio.isEmpty) kayttajanOikeudet diff --git a/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/resource/RaportointiApiConstants.scala b/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/resource/RaportointiApiConstants.scala index 0f812e4f..5ac79013 100644 --- a/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/resource/RaportointiApiConstants.scala +++ b/lambdat/raportointi/src/main/scala/fi/oph/viestinvalitys/raportointi/resource/RaportointiApiConstants.scala @@ -24,6 +24,7 @@ object RaportointiAPIConstants { final val VASTAANOTTAJA_PARAM_NAME = "vastaanottaja" final val ORGANISAATIO_PARAM_NAME = "organisaatio" final val VIESTI_SISALTO_PARAM_NAME = "viesti" + final val PALVELU_PARAM_NAME = "palvelu" final val VIESTI_PATH = VERSIONED_RAPORTOINTI_API_PREFIX + "/viesti" final val VIESTITUNNISTE_PARAM_NAME = "viestiTunniste" @@ -36,6 +37,7 @@ object RaportointiAPIConstants { final val ORGANISAATIOT_PATH = VERSIONED_RAPORTOINTI_API_PREFIX + "/organisaatiot" final val ORGANISAATIOT_OIKEUDET_PATH = ORGANISAATIOT_PATH + "/oikeudet" final val OMAT_TIEDOT_PATH = VERSIONED_RAPORTOINTI_API_PREFIX + "/omattiedot" + final val PALVELUT_PATH = VERSIONED_RAPORTOINTI_API_PREFIX + "/palvelut" /** * Swagger-kuvauksiin liittyvät vakiot diff --git a/shared/src/main/resources/flyway/V202409120000__create_lahettavapalvelu_index.sql b/shared/src/main/resources/flyway/V202409120000__create_lahettavapalvelu_index.sql new file mode 100644 index 00000000..ed32a5ce --- /dev/null +++ b/shared/src/main/resources/flyway/V202409120000__create_lahettavapalvelu_index.sql @@ -0,0 +1,2 @@ +-- lista lähettävistä palveluista muodostetaan toistaiseksi lähetyksien tiedoista, palvelu cachettaa +CREATE INDEX IF NOT EXISTS lahetykset_lahettavapalvelu_index on lahetykset (lahettavapalvelu asc); \ No newline at end of file diff --git a/shared/src/main/scala/fi/oph/viestinvalitys/business/KantaOperaatiot.scala b/shared/src/main/scala/fi/oph/viestinvalitys/business/KantaOperaatiot.scala index ab7a42cd..320f49fa 100644 --- a/shared/src/main/scala/fi/oph/viestinvalitys/business/KantaOperaatiot.scala +++ b/shared/src/main/scala/fi/oph/viestinvalitys/business/KantaOperaatiot.scala @@ -1116,4 +1116,19 @@ class KantaOperaatiot(db: JdbcBackend.JdbcDatabaseDef) { maskit = maskit.get(tunniste).getOrElse(Map.empty), omistaja = omistaja, prioriteetti = Prioriteetti.valueOf(prioriteetti))).headOption + + /** + * Hakee listan lähettävistä palveluista + * + * @return lista lähetyksille tallennetuista lähettävistä palveluista + */ + def getLahettavatPalvelut(): List[String] = + + val lahettavatPalvelutQuery = sql""" + SELECT DISTINCT lahettavapalvelu + FROM lahetykset + ORDER BY lahettavapalvelu ASC + """.as[String] + Await.result(db.run(lahettavatPalvelutQuery), DB_TIMEOUT).toList + } diff --git a/shared/src/test/scala/fi/oph/viestinvalitys/business/KantaOperaatiotTest.scala b/shared/src/test/scala/fi/oph/viestinvalitys/business/KantaOperaatiotTest.scala index 8f2bf634..5464dc4a 100644 --- a/shared/src/test/scala/fi/oph/viestinvalitys/business/KantaOperaatiotTest.scala +++ b/shared/src/test/scala/fi/oph/viestinvalitys/business/KantaOperaatiotTest.scala @@ -1286,4 +1286,18 @@ class KantaOperaatiotTest { kantaOperaatiot.getRaportointiViestiTunnisteella(viesti.tunniste, kayttajanKayttooikeudet).get) // ei käyttöoikeuksia Assertions.assertEquals(Option.empty, kantaOperaatiot.getRaportointiViestiTunnisteella(viesti2.tunniste, kayttajanKayttooikeudet)) + + /** + * Testataan lähettävien palvelujen listan haku + */ + @Test def testGetLahettavatPalvelut(): Unit = + // lähetyksiä eri palveluilla + val lahetys = this.tallennaLahetys(lahettavaPalvelu = "hakemuspalvelu") + val lahetys2 = this.tallennaLahetys(lahettavaPalvelu = "osoitepalvelu") + val lahetys3 = this.tallennaLahetys(lahettavaPalvelu = "jokupalvelu") + // sama palvelu + val lahetys4 = this.tallennaLahetys(lahettavaPalvelu = "hakemuspalvelu") + // Esimerkkipalvelu on alustettuna kantaan + Assertions.assertEquals(4, kantaOperaatiot.getLahettavatPalvelut().size) + Assertions.assertEquals(List("Esimerkkipalvelu", "hakemuspalvelu", "jokupalvelu", "osoitepalvelu"), kantaOperaatiot.getLahettavatPalvelut()) } diff --git a/viestinvalitys-raportointi/package-lock.json b/viestinvalitys-raportointi/package-lock.json index 0d53af44..7ef3e1a6 100644 --- a/viestinvalitys-raportointi/package-lock.json +++ b/viestinvalitys-raportointi/package-lock.json @@ -15,9 +15,10 @@ "@mui/material-nextjs": "^5.15.11", "@mui/x-data-grid": "^6.18.7", "@mui/x-tree-view": "^6.17.0", - "@opetushallitus/oph-design-system": "github:opetushallitus/oph-design-system#v0.0.2", + "@opetushallitus/oph-design-system": "github:opetushallitus/oph-design-system#v0.0.3", "@tanstack/react-query": "^5.51.5", "@tanstack/react-query-devtools": "^5.51.5", + "date-fns-tz": "^3.1.3", "dompurify": "^3.0.11", "i18next": "^23.11.5", "i18next-chained-backend": "^4.6.2", @@ -1780,9 +1781,8 @@ } }, "node_modules/@opetushallitus/oph-design-system": { - "version": "0.0.2", - "resolved": "git+ssh://git@github.com/opetushallitus/oph-design-system.git#56838f9afdcccea63f91652332c484e133e09f69", - "license": "EUPL-1.2", + "version": "0.0.3", + "resolved": "git+ssh://git@github.com/opetushallitus/oph-design-system.git#4033d6e687bbda8706bdd3eed61a9cd959c4e950", "peerDependencies": { "@mui/material": "^5", "next": "^14", @@ -3949,6 +3949,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-tz": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.1.3.tgz", + "integrity": "sha512-ZfbMu+nbzW0mEzC8VZrLiSWvUIaI3aRHeq33mTe7Y38UctKukgqPR4nTDwcwS4d64Gf8GghnVsroBuMY3eiTeA==", + "peerDependencies": { + "date-fns": "^3.0.0" + } + }, "node_modules/debug": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", diff --git a/viestinvalitys-raportointi/package.json b/viestinvalitys-raportointi/package.json index d2bd9dda..78fffb40 100644 --- a/viestinvalitys-raportointi/package.json +++ b/viestinvalitys-raportointi/package.json @@ -17,9 +17,10 @@ "@mui/material-nextjs": "^5.15.11", "@mui/x-data-grid": "^6.18.7", "@mui/x-tree-view": "^6.17.0", - "@opetushallitus/oph-design-system": "github:opetushallitus/oph-design-system#v0.0.2", + "@opetushallitus/oph-design-system": "github:opetushallitus/oph-design-system#v0.0.3", "@tanstack/react-query": "^5.51.5", "@tanstack/react-query-devtools": "^5.51.5", + "date-fns-tz": "^3.1.3", "dompurify": "^3.0.11", "i18next": "^23.11.5", "i18next-chained-backend": "^4.6.2", diff --git a/viestinvalitys-raportointi/src/app/Haku.tsx b/viestinvalitys-raportointi/src/app/Haku.tsx index a6b5b40b..8acc2899 100644 --- a/viestinvalitys-raportointi/src/app/Haku.tsx +++ b/viestinvalitys-raportointi/src/app/Haku.tsx @@ -1,12 +1,8 @@ 'use client'; import { Box, - FormControl, - FormLabel, InputAdornment, - MenuItem, OutlinedInput, - Select, SelectChangeEvent, } from '@mui/material'; import { useDebouncedCallback } from 'use-debounce'; @@ -14,33 +10,34 @@ import { useQueryState } from 'nuqs'; import { Search } from '@mui/icons-material'; import { NUQS_DEFAULT_OPTIONS } from './lib/constants'; import { useTranslation } from './i18n/clientLocalization'; +import { LahettavaPalveluInput } from './components/LahettavaPalveluInput'; +import { OphFormControl } from './components/OphFormControl'; +import { OphSelect } from '@opetushallitus/oph-design-system'; const HakukenttaSelect = ({ + labelId, value: selectedHakukentta, onChange, }: { + labelId: string; value: string; onChange: (e: SelectChangeEvent) => void; }) => { const { t } = useTranslation(); return ( - - - {t('lahetykset.haku.vastaanottaja')} - - - {t('lahetykset.haku.lahettaja')} - - - {t('lahetykset.haku.otsikko-sisalto')} - - + /> ); }; @@ -53,62 +50,91 @@ const HakukenttaInput = ({ }) => { const { t } = useTranslation(); return ( - - {t('lahetykset.haku.mista-haetaan')} - - + ( + + )} + /> ); }; -export default function Haku() { - const [selectedHakukentta, setSelectedHakukentta] = useQueryState("hakukentta", NUQS_DEFAULT_OPTIONS); - const [hakusana, setHakusana] = useQueryState("hakusana", NUQS_DEFAULT_OPTIONS); +export default function Haku({lahettavatPalvelut}: {lahettavatPalvelut: string[]}) { + const [selectedHakukentta, setSelectedHakukentta] = useQueryState( + 'hakukentta', + NUQS_DEFAULT_OPTIONS, + ); + const [hakusana, setHakusana] = useQueryState( + 'hakusana', + NUQS_DEFAULT_OPTIONS, + ); + const [palvelu, setPalvelu] = useQueryState('palvelu', NUQS_DEFAULT_OPTIONS); // päivitetään 3s viiveellä hakuparametrit const handleTypedSearch = useDebouncedCallback((term) => { // päivitetään kun minimipituus täyttyy - if(term.length > 4 || term) { - setHakusana(term) + if (term.length > 4 || term) { + setHakusana(term); } else if (term.length == 0) { - setHakusana(null) // kentän tyhjäys + setHakusana(null); // kentän tyhjäys } }, 3000); const { t } = useTranslation(); return ( - - setSelectedHakukentta(e.target.value)}/> - + - {t('lahetykset.hae')} - { - handleTypedSearch(e.target.value); + setSelectedHakukentta(e.target.value)} + /> + { + return ( + { + handleTypedSearch(e.target.value); + }} + autoFocus={true} + type="text" + endAdornment={ + + + + } + /> + ); }} - autoFocus={true} - type="text" - endAdornment={ - - - - } /> - + + + setPalvelu(e.target.value)} + palvelut={lahettavatPalvelut} /> + ); } diff --git a/viestinvalitys-raportointi/src/app/components/ClientSpinner.tsx b/viestinvalitys-raportointi/src/app/components/ClientSpinner.tsx new file mode 100644 index 00000000..7e11d0c1 --- /dev/null +++ b/viestinvalitys-raportointi/src/app/components/ClientSpinner.tsx @@ -0,0 +1,9 @@ +'use client'; + +import { CircularProgress, CircularProgressProps } from '@mui/material'; +import { useTranslation } from '../i18n/clientLocalization'; + +export const ClientSpinner = (props: CircularProgressProps) => { + const { t } = useTranslation(); + return ; +}; diff --git a/viestinvalitys-raportointi/src/app/components/GreyDivider.tsx b/viestinvalitys-raportointi/src/app/components/GreyDivider.tsx index 9a95ed3f..b8074b55 100644 --- a/viestinvalitys-raportointi/src/app/components/GreyDivider.tsx +++ b/viestinvalitys-raportointi/src/app/components/GreyDivider.tsx @@ -1,10 +1,10 @@ 'use client'; import { Divider } from "@mui/material"; -import { colors } from "../theme"; +import { ophColors } from "@opetushallitus/oph-design-system"; export const GreyDivider = () => { return ( - + ); }; diff --git a/viestinvalitys-raportointi/src/app/components/HomeIconLink.tsx b/viestinvalitys-raportointi/src/app/components/HomeIconLink.tsx index 038cc1f6..d40cda9c 100644 --- a/viestinvalitys-raportointi/src/app/components/HomeIconLink.tsx +++ b/viestinvalitys-raportointi/src/app/components/HomeIconLink.tsx @@ -1,18 +1,18 @@ 'use client'; import { HomeOutlined } from '@mui/icons-material'; -import { Button } from '@opetushallitus/oph-design-system'; +import { OphButton } from '@opetushallitus/oph-design-system'; import { useTranslation } from '../i18n/clientLocalization'; const HomeIconLink = () => { const { t } = useTranslation(); return ( - } aria-label={t('yleinen.palaa.etusivulle')} href="/" sx={{ border: '1px solid', borderRadius: '5px', width: 30, height: 30 }} > - + ); }; diff --git a/viestinvalitys-raportointi/src/app/components/LahettavaPalveluInput.tsx b/viestinvalitys-raportointi/src/app/components/LahettavaPalveluInput.tsx new file mode 100644 index 00000000..677c275b --- /dev/null +++ b/viestinvalitys-raportointi/src/app/components/LahettavaPalveluInput.tsx @@ -0,0 +1,59 @@ +'use client'; +import { OphFormControl } from './OphFormControl'; +import { useTranslation } from '../i18n/clientLocalization'; +import { SelectChangeEvent } from '@mui/material'; +import { OphSelect } from '@opetushallitus/oph-design-system'; + +const LahettavaPalveluSelect = ({ + labelId, + value: selectedPalvelu, + options, + onChange, +}: { + labelId: string; + value: string; + options: string[]; + onChange: (e: SelectChangeEvent) => void; +}) => { + const palveluOptions = options.map((palvelu) => { + return { value: palvelu, label: palvelu }; + }); + + return ( + + ); +}; + +export const LahettavaPalveluInput = ({ + value, + onChange, + palvelut +}: { + value: string; + onChange: (e: SelectChangeEvent) => void; + palvelut: string[]; +}) => { + const { t } = useTranslation(); + + return ( + ( + + )} + /> + ); +}; diff --git a/viestinvalitys-raportointi/src/app/components/LahetysStatus.tsx b/viestinvalitys-raportointi/src/app/components/LahetysStatus.tsx index a0029662..3131565f 100644 --- a/viestinvalitys-raportointi/src/app/components/LahetysStatus.tsx +++ b/viestinvalitys-raportointi/src/app/components/LahetysStatus.tsx @@ -3,17 +3,17 @@ import { CheckCircle, Error, Warning, WatchLater } from '@mui/icons-material'; import { Status, LahetyksenVastaanottoTila } from '../lib/types'; import { getLahetyksenVastaanottajia, getLahetysStatus, getVastaanottajatPerStatus } from '../lib/util'; import { Box } from '@mui/material'; -import { aliasColors, colors } from '../theme'; import { useTranslation } from '../i18n/clientLocalization'; +import { ophColors } from '@opetushallitus/oph-design-system'; export const StatusIcon = ({ status }: { status: string }) => { switch (status) { case Status.EPAONNISTUI: - return ; + return ; case Status.KESKEN: - return ; + return ; case Status.ONNISTUI: - return ; + return ; default: return ; } diff --git a/viestinvalitys-raportointi/src/app/components/LocalDateTime.tsx b/viestinvalitys-raportointi/src/app/components/LocalDateTime.tsx index 2c76ab11..47e8ad3c 100644 --- a/viestinvalitys-raportointi/src/app/components/LocalDateTime.tsx +++ b/viestinvalitys-raportointi/src/app/components/LocalDateTime.tsx @@ -1,19 +1,34 @@ 'use client'; +import { useEffect, useState } from "react"; +import { format, toZonedTime } from 'date-fns-tz'; +import { OphTypography } from "@opetushallitus/oph-design-system"; + +// päivämääräformatoinnin hydraatio-ongelman taklaus +// ks. https://nextjs.org/docs/messages/react-hydration-error#solution-1-using-useeffect-to-run-on-the-client-only +export default function LocalDateTime({date}: {date: string}) { + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + }, []); + + return {isClient ? toFormattedDateTimeString(date) : ''}; +} + +function toFormattedDateTimeString(value: string): string { + try { + const zonedDate = toZonedTime(new Date(value), 'Europe/Helsinki'); + return format(zonedDate, 'd.M.yyyy HH:mm', { + timeZone: 'Europe/Helsinki', + }); + } catch (error) { + console.warn( + 'Caught error when trying to format date, returning empty string', + ); + return ''; + } +} + + + -import { Suspense } from 'react'; -import { useHydration } from '../hooks/useHydration'; - -// pieni kikkailu SSR hydration-ongelman välttämiseksi -// ks. https://francoisbest.com/posts/2023/displaying-local-times-in-nextjs -const LocalDateTime = ({ date }: { date: string }) => { - const hydrated = useHydration(); - return ( - - - {new Date(date).toLocaleString()} - {hydrated ? '' : ' (UTC)'} - - - ); -}; -export default LocalDateTime; diff --git a/viestinvalitys-raportointi/src/app/components/MainContainer.tsx b/viestinvalitys-raportointi/src/app/components/MainContainer.tsx index a5d0dfc6..255a3379 100644 --- a/viestinvalitys-raportointi/src/app/components/MainContainer.tsx +++ b/viestinvalitys-raportointi/src/app/components/MainContainer.tsx @@ -1,13 +1,16 @@ 'use client'; import { styled } from '@mui/material/styles'; import { Box } from '@mui/material'; -import { DEFAULT_BOX_BORDER, colors, withDefaultProps } from '@/app/theme'; +import { ophColors } from '@opetushallitus/oph-design-system'; +import { withDefaultProps } from '../theme'; + +export const DEFAULT_BOX_BORDER = `2px solid ${ophColors.grey100}`; export const MainContainer = withDefaultProps( styled(Box)(({ theme }) => ({ padding: theme.spacing(4), border: DEFAULT_BOX_BORDER, - backgroundColor: colors.white, + backgroundColor: ophColors.white, })), { component: 'main', diff --git a/viestinvalitys-raportointi/src/app/components/NavAppBar.tsx b/viestinvalitys-raportointi/src/app/components/NavAppBar.tsx index 179815cf..b8099eb6 100644 --- a/viestinvalitys-raportointi/src/app/components/NavAppBar.tsx +++ b/viestinvalitys-raportointi/src/app/components/NavAppBar.tsx @@ -4,9 +4,8 @@ import NavigateNextIcon from '@mui/icons-material/NavigateNext'; import OrganisaatioSelect from './OrganisaatioSelect'; import { Suspense } from 'react'; import Loading from './Loading'; -import { colors } from '../theme'; import { PageContent } from './PageContent'; -import { Typography } from '@opetushallitus/oph-design-system'; +import { ophColors, OphTypography } from '@opetushallitus/oph-design-system'; import { usePathname } from 'next/navigation'; import { useTranslation } from '../i18n/clientLocalization'; @@ -18,9 +17,9 @@ export default function Header() { {isHome ? ( - {t('lahetykset.otsikko')} + {t('lahetykset.otsikko')} ) : ( <> - - + + {t('navigointi.lahetykset')} - + > )} }> diff --git a/viestinvalitys-raportointi/src/app/components/OphFormControl.tsx b/viestinvalitys-raportointi/src/app/components/OphFormControl.tsx new file mode 100644 index 00000000..95f8d97a --- /dev/null +++ b/viestinvalitys-raportointi/src/app/components/OphFormControl.tsx @@ -0,0 +1,40 @@ +'use client'; +import { FormControl, FormControlProps, FormHelperText, FormLabel, styled } from "@mui/material"; +import { useId } from "react"; + +const StyledFormHelperText = styled(FormHelperText)(({ theme }) => ({ + margin: theme.spacing(0.5, 0), + })); + + const EMPTY_ARRAY: Array = []; + + export const OphFormControl = ({ + label, + renderInput, + helperText, + errorMessages = EMPTY_ARRAY as Array, + ...props + }: Omit & { + label: string; + helperText?: string; + errorMessages?: Array; + renderInput: (props: { labelId: string }) => React.ReactNode; + }) => { + const id = useId(); + const labelId = `OphFormControl-${id}-label`; + return ( + + {label} + {helperText && ( + {helperText} + )} + {renderInput({ labelId })} + {errorMessages.map((message, index) => ( + + {message} + + ))} + + ); + }; + \ No newline at end of file diff --git a/viestinvalitys-raportointi/src/app/components/OrganisaatioSelect.tsx b/viestinvalitys-raportointi/src/app/components/OrganisaatioSelect.tsx index 8deee4df..e2db96a5 100644 --- a/viestinvalitys-raportointi/src/app/components/OrganisaatioSelect.tsx +++ b/viestinvalitys-raportointi/src/app/components/OrganisaatioSelect.tsx @@ -14,11 +14,12 @@ import { } from '../lib/util'; import OrganisaatioHierarkia from './OrganisaatioHierarkia'; import { useQuery } from '@tanstack/react-query'; -import { Typography } from '@opetushallitus/oph-design-system'; +import { OphTypography } from '@opetushallitus/oph-design-system'; import { NUQS_DEFAULT_OPTIONS } from '../lib/constants'; import { useTranslation } from '../i18n/clientLocalization'; import { useLocale } from '../i18n/locale-provider'; import { getLocale } from '../i18n/localization'; +import { ClientSpinner } from './ClientSpinner'; export const StyledDrawer = styled(Drawer)(({theme}) => ({ '& .MuiDrawer-paper': { @@ -114,9 +115,9 @@ const OrganisaatioSelect = () => { const lng = useLocale(); return ( <> - + {translateOrgName(selectedOrg, lng)} - + @@ -127,11 +128,11 @@ const OrganisaatioSelect = () => { sx={{ padding: 2 }} > - + {t('organisaatio.valittu', {organisaatioNimi: translateOrgName(selectedOrg, lng)})} - + {isLoading ? ( - {t('yleinen.ladataan')} + ) : ( { - setHydrated(true); - }, []); - return hydrated; -} diff --git a/viestinvalitys-raportointi/src/app/i18n/locales/fi.json b/viestinvalitys-raportointi/src/app/i18n/locales/fi.json index 61aa8477..1ed947dc 100644 --- a/viestinvalitys-raportointi/src/app/i18n/locales/fi.json +++ b/viestinvalitys-raportointi/src/app/i18n/locales/fi.json @@ -13,6 +13,7 @@ "mista-haetaan": "Mistä haetaan", "vastaanottaja": "Vastaanottaja", "lahettaja": "Lähettäjä", + "lahettava-palvelu": "Viestin lähettänyt palvelu", "otsikko-sisalto": "Otsikko ja sisältö", "eituloksia": "Hakuehdoilla ei löytynyt tuloksia", "tila": "Tila", @@ -65,6 +66,8 @@ "error": { "404.otsikko": "Sivua ei löytynyt", "404.teksti": "Etsittyä sivua ei löytynyt.", + "access-denied": "Ei riittäviä käyttöoikeuksia", + "fetch": "Tietojen haussa tapahtui virhe", "otsikko": "Tapahtui virhe", "teksti": "Palvelussa tapahtui virhe." }, diff --git a/viestinvalitys-raportointi/src/app/lahetys/[tunniste]/Vastaanottajat.tsx b/viestinvalitys-raportointi/src/app/lahetys/[tunniste]/Vastaanottajat.tsx index ee18fe9a..fc336975 100644 --- a/viestinvalitys-raportointi/src/app/lahetys/[tunniste]/Vastaanottajat.tsx +++ b/viestinvalitys-raportointi/src/app/lahetys/[tunniste]/Vastaanottajat.tsx @@ -10,7 +10,7 @@ import { getLahetysStatus } from '../../lib/util'; import { StatusIcon } from '@/app/components/LahetysStatus'; import ViewViesti from './ViewViesti'; import { StyledCell, StyledHeaderCell, StyledTable, StyledTableBody } from '@/app/components/StyledTable'; -import { Typography } from '@opetushallitus/oph-design-system'; +import { OphTypography } from '@opetushallitus/oph-design-system'; import { useTranslation } from '@/app/i18n/clientLocalization'; const VastaanottajanStatus = ({ @@ -33,7 +33,7 @@ const VastaanottajanStatus = ({ const Toiminnot = ({ tila }: { tila: VastaanotonTila }) => { const { t } = useTranslation(); if (getLahetysStatus([tila]) === Status.EPAONNISTUI) { - return {t('vastaanottaja.laheta-uudelleen')}; + return {t('vastaanottaja.laheta-uudelleen')}; } return <>>; }; diff --git a/viestinvalitys-raportointi/src/app/lahetys/[tunniste]/ViewViesti.tsx b/viestinvalitys-raportointi/src/app/lahetys/[tunniste]/ViewViesti.tsx index b368f56e..4a70a5ec 100644 --- a/viestinvalitys-raportointi/src/app/lahetys/[tunniste]/ViewViesti.tsx +++ b/viestinvalitys-raportointi/src/app/lahetys/[tunniste]/ViewViesti.tsx @@ -13,16 +13,15 @@ import { import { fetchViesti } from '@/app/lib/data'; import { Viesti } from '@/app/lib/types'; import { useQuery } from '@tanstack/react-query'; -import { Button, Typography } from '@opetushallitus/oph-design-system'; +import { OphButton, ophColors, OphTypography } from '@opetushallitus/oph-design-system'; import CloseIcon from '@mui/icons-material/Close'; -import { colors } from '@/app/theme'; import { useTranslation } from '@/app/i18n/clientLocalization'; const closeButtonStyle = { position: 'absolute', right: "2px", top: "2px", - color: colors.grey500, + color: ophColors.grey500, }; const ViestiModal = ({ @@ -39,14 +38,13 @@ const ViestiModal = ({ return response; }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { data, isLoading, error, refetch } = useQuery({ + const { data, isLoading } = useQuery({ queryKey: ['fetchViesti', viestiTunniste], queryFn: () => doFetchViesti(viestiTunniste), }); const { t } = useTranslation(); if (isLoading) { - return {t('yleinen.ladataan')}; + return {t('yleinen.ladataan')}; } return ( - + {data?.otsikko ?? t('viesti.ei-otsikkoa')} @@ -72,14 +70,14 @@ const ViestiModal = ({ }} /> ) : ( - + {data?.sisalto ?? t('viesti.ei-sisaltoa')} - + )} - {t('yleinen.sulje')} + {t('yleinen.sulje')} ); @@ -101,7 +99,7 @@ export default function ViewViesti({ return ( <> - {t('viesti.nayta')} + {t('viesti.nayta')} {viestiOpen ? ( { +const LahetyksenTiedot = async ({ lahetys }: { lahetys: Lahetys }) => { + const { t } = await initTranslations(); return ( diff --git a/viestinvalitys-raportointi/src/app/layout.tsx b/viestinvalitys-raportointi/src/app/layout.tsx index ae36418e..a243bae3 100644 --- a/viestinvalitys-raportointi/src/app/layout.tsx +++ b/viestinvalitys-raportointi/src/app/layout.tsx @@ -1,12 +1,12 @@ import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter'; import NavAppBar from './components/NavAppBar'; -import { CssBaseline, ThemeProvider } from '@mui/material'; -import theme from './theme'; +import { CssBaseline } from '@mui/material'; import ReactQueryClientProvider from './components/react-query-client-provider'; import { PageLayout } from './components/PageLayout'; import { getLocale, initTranslations } from './i18n/localization'; import type { Metadata } from 'next'; import { LocaleProvider } from './i18n/locale-provider'; +import { OphNextJsThemeProvider } from '@opetushallitus/oph-design-system/next/theme'; export async function generateMetadata(): Promise { const { t } = await initTranslations(); @@ -27,12 +27,12 @@ export default async function RootLayout({ - + }>{children} - + diff --git a/viestinvalitys-raportointi/src/app/lib/data.ts b/viestinvalitys-raportointi/src/app/lib/data.ts index e59bb97f..a52549a6 100644 --- a/viestinvalitys-raportointi/src/app/lib/data.ts +++ b/viestinvalitys-raportointi/src/app/lib/data.ts @@ -1,21 +1,14 @@ 'use server'; // täytyy olla eksplisiittisesti koska käytetään client-komponentista react-querylla -import { cookies } from 'next/headers'; import { LahetysHakuParams, OrganisaatioSearchResult, VastaanottajatHakuParams } from './types'; -import { apiUrl, cookieName, loginUrl, virkailijaUrl } from './configurations'; -import { redirect } from 'next/navigation'; +import { apiUrl, virkailijaUrl } from './configurations'; +import { makeRequest } from './http-client'; const LAHETYKSET_SIVUTUS_KOKO = 20; const VASTAANOTTAJAT_SIVUTUS_KOKO = 10; const REVALIDATE_TIME_SECONDS = 60 * 60 * 2; const REVALIDATE_ASIOINTIKIELI = 60; -// TODO apuwrapperi headerien asettamiseen ja virheenkäsittelyyn export async function fetchLahetykset(hakuParams: LahetysHakuParams) { - const sessionCookie = cookies().get(cookieName); - if (sessionCookie === undefined) { - console.info('no session cookie, redirect to login'); - redirect(loginUrl); - } const fetchUrlBase = `${apiUrl}/lahetykset/lista?enintaan=${LAHETYKSET_SIVUTUS_KOKO}`; // eslint-disable-next-line no-var var fetchParams = hakuParams.seuraavatAlkaen @@ -27,53 +20,27 @@ export async function fetchLahetykset(hakuParams: LahetysHakuParams) { if (hakuParams?.organisaatio) { fetchParams += `&organisaatio=${hakuParams.organisaatio}`; } - const cookieParam = sessionCookie.name + '=' + sessionCookie.value; - const res = await fetch(fetchUrlBase.concat(fetchParams), { - headers: { cookie: cookieParam ?? '' }, // Forward the authorization header + if(hakuParams?.palvelu) { + fetchParams += `&palvelu=${hakuParams.palvelu}`; + } + const res = await makeRequest(fetchUrlBase.concat(fetchParams), { cache: 'no-store', }); - if (!(res.ok || res.status === 400 || res.status === 410)) { - if (res.status === 401) { - console.info('http 401, redirect to login'); - redirect(loginUrl); - } - // This will activate the closest `error.js` Error Boundary - throw new Error(res.statusText); - } - return res.json(); + return res.data; } export async function fetchLahetys(lahetysTunnus: string) { - const sessionCookie = cookies().get(cookieName); - if (sessionCookie === undefined) { - console.info('no session cookie, redirect to login'); - redirect(loginUrl); - } const url = `${apiUrl}/lahetykset/${lahetysTunnus}`; - const cookieParam = sessionCookie.name + '=' + sessionCookie.value; - const res = await fetch(url, { - headers: { cookie: cookieParam ?? '' }, // Forward the authorization header + const res = await makeRequest(url, { cache: 'no-store', }); - if (!(res.ok || res.status === 400 || res.status === 410)) { - if (res.status === 401) { - redirect(loginUrl); - } - // This will activate the closest `error.js` Error Boundary - throw new Error(res.statusText); - } - return res.json(); + return res.data; } export async function fetchLahetyksenVastaanottajat( lahetysTunnus: string, hakuParams: VastaanottajatHakuParams, ) { - const sessionCookie = cookies().get(cookieName); - if (sessionCookie === undefined) { - console.info('no session cookie, redirect to login'); - redirect(loginUrl); - } const url = `${apiUrl}/lahetykset/${lahetysTunnus}/vastaanottajat?enintaan=${VASTAANOTTAJAT_SIVUTUS_KOKO}`; // eslint-disable-next-line no-var var fetchParams = hakuParams.alkaen @@ -88,90 +55,38 @@ export async function fetchLahetyksenVastaanottajat( if (hakuParams?.organisaatio) { fetchParams += `&organisaatio=${hakuParams.organisaatio}`; } - const cookieParam = sessionCookie.name + '=' + sessionCookie.value; - const res = await fetch(url.concat(fetchParams), { - headers: { cookie: cookieParam ?? '' }, // Forward the authorization header + const res = await makeRequest(url.concat(fetchParams), { cache: 'no-store', }); - if (!(res.ok || res.status === 400 || res.status === 410)) { - if (res.status === 401) { - redirect(loginUrl); - } - // This will activate the closest `error.js` Error Boundary - throw new Error(res.statusText); - } - return res.json(); + return res.data; } export async function fetchMassaviesti(lahetysTunnus: string) { - const sessionCookie = cookies().get(cookieName); - if (sessionCookie === undefined) { - console.info('no session cookie, redirect to login'); - redirect(loginUrl); - } const url = `${apiUrl}/massaviesti/${lahetysTunnus}`; - const cookieParam = sessionCookie.name + '=' + sessionCookie.value; - const res = await fetch(url, { - headers: { cookie: cookieParam ?? '' }, // Forward the authorization header + const res = await makeRequest(url, { cache: 'no-store', }); - if (!(res.ok || res.status === 400 || res.status === 410)) { - if (res.status === 401) { - redirect(loginUrl); - } - // This will activate the closest `error.js` Error Boundary - throw new Error(res.statusText); - } - return res.json(); + return res.data; } export async function fetchViesti(viestiTunnus: string) { - const sessionCookie = cookies().get(cookieName); - if (sessionCookie === undefined) { - console.info('no session cookie, redirect to login'); - redirect(loginUrl); - } const url = `${apiUrl}/viesti/${viestiTunnus}`; - const cookieParam = sessionCookie.name + '=' + sessionCookie.value; - const res = await fetch(url, { - headers: { cookie: cookieParam ?? '' }, // Forward the authorization header + const res = await makeRequest(url, { cache: 'no-store', }); - console.info(res.status); - if (!(res.ok || res.status === 400 || res.status === 410)) { - if (res.status === 401) { - redirect(loginUrl); - } - // This will activate the closest `error.js` Error Boundary - throw new Error(res.statusText); - } - return res.json(); + return res.data; } export async function fetchAsiointikieli() { - const sessionCookie = cookies().get(cookieName); - if (sessionCookie === undefined) { - console.info('no session cookie, redirect to login'); - console.info(loginUrl); - redirect(loginUrl); - } const url = `${apiUrl}/omattiedot`; - const cookieParam = sessionCookie.name + '=' + sessionCookie.value; - const res = await fetch(url, { - headers: { cookie: cookieParam ?? '' }, // Forward the authorization header + const res = await makeRequest(url, { next: { revalidate: REVALIDATE_ASIOINTIKIELI } }); - if (!(res.ok || res.status === 400 || res.status === 410)) { - if (res.status === 401) { - redirect(loginUrl); - } - // This will activate the closest `error.js` Error Boundary - throw new Error('asiointikielen haku epäonnistui'); - } - return res.json(); + return res.data; } export async function fetchLokalisaatiot(lang: string) { + // ei tarvi käyttää http-clientia kun ei vaadi tunnistautumista ja on tiedostot fallbackina const url = `${virkailijaUrl}/lokalisointi/cxf/rest/v1/localisation?category=viestinvalitys&locale=`; const res = await fetch(`${url}${lang}`, { next: { revalidate: REVALIDATE_TIME_SECONDS }, @@ -180,34 +95,15 @@ export async function fetchLokalisaatiot(lang: string) { } export async function fetchOrganisaatioRajoitukset() { - const sessionCookie = cookies().get(cookieName); - if (sessionCookie === undefined) { - console.info('no session cookie, redirect to login'); - redirect(loginUrl); - } const url = `${apiUrl}/organisaatiot/oikeudet`; - const cookieParam = sessionCookie.name + '=' + sessionCookie.value; - const res = await fetch(url, { - headers: { cookie: cookieParam ?? '' }, // Forward the authorization header - cache: 'no-store', // caching in backend + const res = await makeRequest(url, { + cache: 'no-store', }); - if (!(res.ok || res.status === 400 || res.status === 410)) { - if (res.status === 401) { - redirect(loginUrl); - } - // This will activate the closest `error.js` Error Boundary - throw new Error('organisaatio-oikeuksien haku epäonnistui'); - } - return res.json(); + return res.data; } export async function searchOrganisaatio(searchStr: string): Promise { console.info('haetaan organisaatiota'); - const sessionCookie = cookies().get(cookieName); - if (sessionCookie === undefined) { - console.info('no session cookie, redirect to login'); - redirect(loginUrl); - } // organisaatiorajaus oikeuksien mukaan const oidRestrictionList: string[] = await fetchOrganisaatioRajoitukset(); const oidRestrictionParams = @@ -221,9 +117,13 @@ export async function searchOrganisaatio(searchStr: string): Promise { + const url = `${apiUrl}/palvelut`; + const res = await makeRequest(url, { + cache: 'no-store', + }); + return res.data; +} diff --git a/viestinvalitys-raportointi/src/app/lib/error-handling.ts b/viestinvalitys-raportointi/src/app/lib/error-handling.ts new file mode 100644 index 00000000..fb22f48c --- /dev/null +++ b/viestinvalitys-raportointi/src/app/lib/error-handling.ts @@ -0,0 +1,16 @@ +export class FetchError extends Error { + response: Response; + constructor(response: Response, message: string = 'error.fetch') { + super(message); + // Set the prototype explicitly. + Object.setPrototypeOf(this, FetchError.prototype); + this.response = response; + } + } + + export class PermissionError extends Error { + constructor(message: string = 'error.access-denied') { + super(message); + } + } + \ No newline at end of file diff --git a/viestinvalitys-raportointi/src/app/lib/http-client.ts b/viestinvalitys-raportointi/src/app/lib/http-client.ts new file mode 100644 index 00000000..e92fe81b --- /dev/null +++ b/viestinvalitys-raportointi/src/app/lib/http-client.ts @@ -0,0 +1,101 @@ +import { redirect } from 'next/navigation'; +import { apiUrl, cookieName, loginUrl } from './configurations'; +import { FetchError } from "./error-handling"; +import { cookies } from 'next/headers'; + +/* sovellettu valintojen-totettaminen repon vastaavasta */ +const doFetch = async (request: Request) => { + try { + const response = await fetch(request); + return response.status >= 400 + ? Promise.reject(new FetchError(response)) + : Promise.resolve(response); + } catch (e) { + return Promise.reject(e); + } + }; + + const isUnauthenticated = (response: Response) => { + return response?.status === 401; + }; + + const isRedirected = (response: Response) => { + return response.redirected; + }; + + const makeBareRequest = (request: Request) => { + request.headers.set('Caller-Id', '1.2.246.562.10.00000000001.viestinvalityspalvelu'); + request.headers.set('CSRF', '1.2.246.562.10.00000000001.viestinvalityspalvelu') + return doFetch(request); + }; + + const retryWithLogin = async (request: Request, loginUrl: string) => { + await makeBareRequest(new Request(loginUrl)); + return makeBareRequest(request); + }; + + const responseToData = async (res: Response) => { + if (res.status === 204) { + return { data: {} }; + } + try { + const result = { data: await res.json() }; // toistaiseksi kaikki kutsuttavat apit palauttavat jsonia + return result; + } catch (e) { + console.error('Parsing fetch response body as JSON failed!'); + return Promise.reject(e); + } + }; + + export const makeRequest = async (url: string, options: RequestInit = {}) => { + console.info('Tehdään request urliin ' + url) + // autentikointicookie + const sessionCookie = cookies().get(cookieName); + if (sessionCookie === undefined) { + redirect(loginUrl); + } + const cookieParam = sessionCookie.name + '=' + sessionCookie.value; + const request = new Request(url, { method: 'GET', headers: { cookie: cookieParam ?? '' }, ...options }) + const originalRequest = request.clone(); + try { + const response = await makeBareRequest(request); + const responseUrl = new URL(response.url); + if ( + isRedirected(response) && + responseUrl.pathname.startsWith('/cas/login') + ) { + redirect(loginUrl); + } + return responseToData(response); + } catch (error: unknown) { + console.error('Virhe tietojen haussa') + if (error instanceof FetchError) { + console.error(error.response.status) + console.error(error.message) + if (isUnauthenticated(error.response)) { + try { + if (request?.url?.includes(apiUrl)) { + const resp = await retryWithLogin(request, loginUrl); + return responseToData(resp); + } + } catch (e) { + if (e instanceof FetchError && isUnauthenticated(e.response)) { + redirect(loginUrl); + } + return Promise.reject(e); + } + } else if ( + isRedirected(error.response) && + error.response.url === request.url + ) { + //Some backend services lose the original method and headers, so we need to do retry with cloned request + const response = await makeBareRequest(originalRequest); + return responseToData(response); + } + } + return Promise.reject(error); + } + }; + + + diff --git a/viestinvalitys-raportointi/src/app/lib/searchParams.ts b/viestinvalitys-raportointi/src/app/lib/searchParams.ts index f435b394..f08bbe00 100644 --- a/viestinvalitys-raportointi/src/app/lib/searchParams.ts +++ b/viestinvalitys-raportointi/src/app/lib/searchParams.ts @@ -4,6 +4,7 @@ export const searchParamsCache = createSearchParamsCache({ seuraavatAlkaen: parseAsString, hakukentta: parseAsString, hakusana: parseAsString, + palvelu: parseAsString, organisaatio: parseAsString, alkaen: parseAsString, tila: parseAsString diff --git a/viestinvalitys-raportointi/src/app/lib/types.ts b/viestinvalitys-raportointi/src/app/lib/types.ts index e92a5907..c9194f6e 100644 --- a/viestinvalitys-raportointi/src/app/lib/types.ts +++ b/viestinvalitys-raportointi/src/app/lib/types.ts @@ -2,6 +2,7 @@ export type LahetysHakuParams = { seuraavatAlkaen: string | null; hakukentta: string | null; hakusana: string | null; + palvelu: string | null; organisaatio: string | null; }; diff --git a/viestinvalitys-raportointi/src/app/page.tsx b/viestinvalitys-raportointi/src/app/page.tsx index 3ebe5eb9..3b006acf 100644 --- a/viestinvalitys-raportointi/src/app/page.tsx +++ b/viestinvalitys-raportointi/src/app/page.tsx @@ -9,7 +9,7 @@ import VirheAlert from './components/VirheAlert'; import LahetyksetTable from './LahetyksetTable'; import LahetyksetSivutus from './LahetyksetSivutus'; import { LahetysHakuParams } from './lib/types'; -import { fetchLahetykset } from './lib/data'; +import { fetchLahettavatPalvelut, fetchLahetykset } from './lib/data'; import { initTranslations } from './i18n/localization'; const Lahetykset = async () => { @@ -17,6 +17,7 @@ const Lahetykset = async () => { seuraavatAlkaen: searchParamsCache.get('seuraavatAlkaen'), hakukentta: searchParamsCache.get('hakukentta'), hakusana: searchParamsCache.get('hakusana'), + palvelu: searchParamsCache.get('palvelu'), organisaatio: searchParamsCache.get('organisaatio'), } const data = await fetchLahetykset(fetchParams); @@ -45,10 +46,11 @@ type PageProps = { export default async function Page({ searchParams }: PageProps) { searchParamsCache.parse(searchParams) // pitää alustaa tässä jotta toimii lahetykset-komponentissa + const palvelut = await fetchLahettavatPalvelut(); return ( }> - + diff --git a/viestinvalitys-raportointi/src/app/theme.tsx b/viestinvalitys-raportointi/src/app/theme.tsx index 2012d827..57b27924 100644 --- a/viestinvalitys-raportointi/src/app/theme.tsx +++ b/viestinvalitys-raportointi/src/app/theme.tsx @@ -1,29 +1,5 @@ 'use client'; import * as React from 'react'; -import { MUI_NEXTJS_OVERRIDES } from '@opetushallitus/oph-design-system/next/theme'; -import { createStyled } from '@mui/system'; -import { deepmerge } from '@mui/utils'; - -import { createODSTheme } from '@opetushallitus/oph-design-system/theme'; - -import { colors, aliasColors } from '@opetushallitus/oph-design-system'; - -export { colors, aliasColors }; - -export const DEFAULT_BOX_BORDER = `2px solid ${colors.grey100}`; - -const theme = createODSTheme({ - variant: 'oph', - overrides: deepmerge(MUI_NEXTJS_OVERRIDES, { - components: { - MuiButtonBase: { - defaultProps: { - disableRipple: true, - }, - }, - }, - }), -}); // MUI:sta (Emotionista) puuttuu styled-componentsin .attrs // Tällä voi asettaa oletus-propsit ilman, että tarvii luoda välikomponenttia @@ -41,6 +17,3 @@ export function withDefaultProps( return ComponentWithDefaultProps; } -export const styled = createStyled({ defaultTheme: theme }); - -export default theme;
( return ComponentWithDefaultProps; } -export const styled = createStyled({ defaultTheme: theme }); - -export default theme;