From da28f483d01ac52e88efd4853a21a983910602df Mon Sep 17 00:00:00 2001 From: Marja Kari Date: Thu, 1 Feb 2024 10:59:09 +0200 Subject: [PATCH 1/8] =?UTF-8?q?haku=20vastaanottajan=20emaililla,=20virhee?= =?UTF-8?q?nk=C3=A4sittely=C3=A4,=20l=C3=A4hetysten=20haun=20refaktorointi?= =?UTF-8?q?a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fi/oph/viestinvalitys/LocalUtil.scala | 4 +- .../resource/LahetysResource.scala | 14 +++- .../resource/RaportointiApiConstants.scala | 2 + .../business/KantaOperaatiot.scala | 51 ++++++++---- .../business/KantaOperaatiotTest.scala | 30 +++++++ viestinvalitys-raportointi/README.md | 11 ++- viestinvalitys-raportointi/next.config.js | 27 ++++++ viestinvalitys-raportointi/src/app/Haku.tsx | 22 ++--- .../src/app/Lahetykset.tsx | 83 +++++++++++-------- .../src/app/LahetyksetSivutus.tsx | 2 +- .../app/{ => components}/LahetysStatus.tsx | 4 +- .../app/{ => components}/LocalDateTime.tsx | 2 +- .../src/app/components/VirheAlert.tsx | 14 ++++ viestinvalitys-raportointi/src/app/error.tsx | 1 - .../lahetys/[tunniste]/VastaanottajatGrid.tsx | 1 - .../src/app/lahetys/[tunniste]/actions.ts | 12 +++ .../src/app/lahetys/[tunniste]/error.tsx | 1 - .../src/app/lahetys/[tunniste]/page.tsx | 8 +- .../src/app/lib/data.ts | 20 +++-- .../src/app/lib/types.ts | 1 - viestinvalitys-raportointi/src/app/page.tsx | 17 ++-- 21 files changed, 229 insertions(+), 98 deletions(-) rename viestinvalitys-raportointi/src/app/{ => components}/LahetysStatus.tsx (83%) rename viestinvalitys-raportointi/src/app/{ => components}/LocalDateTime.tsx (90%) create mode 100644 viestinvalitys-raportointi/src/app/components/VirheAlert.tsx create mode 100644 viestinvalitys-raportointi/src/app/lahetys/[tunniste]/actions.ts diff --git a/integraatio/src/test/scala/fi/oph/viestinvalitys/LocalUtil.scala b/integraatio/src/test/scala/fi/oph/viestinvalitys/LocalUtil.scala index be86d6d7..670798a5 100644 --- a/integraatio/src/test/scala/fi/oph/viestinvalitys/LocalUtil.scala +++ b/integraatio/src/test/scala/fi/oph/viestinvalitys/LocalUtil.scala @@ -171,7 +171,7 @@ object LocalUtil { "omistaja", "hakemuspalvelu", Option.apply("0.1.2.3"), - Kontakti(Option.apply("Testi Virkailija"+counter), "testi.virkailija@oph.fi"+counter), + Kontakti(Option.apply("Testi Virkailija"+counter), "testi.virkailija"+counter+"@oph.fi"), Option.apply("no-reply@opintopolku.fi"), Prioriteetti.NORMAALI, 365 @@ -199,7 +199,7 @@ object LocalUtil { }) // lähetys jossa samalla viestillä useita vastaanottajia val lahetys2 = kantaOperaatiot.tallennaLahetys( - "Testiotsikko2", + "Massalähetysotsikko", "omistaja", "hakemuspalvelu", Option.apply("0.1.2.3"), 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 502f5e92..feb1c860 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 @@ -121,11 +121,13 @@ class LahetysResource { )) def lueLahetykset(@RequestParam(name = ALKAEN_PARAM_NAME, required = false) alkaen: Optional[String], @RequestParam(name = ENINTAAN_PARAM_NAME, required = false) enintaan: Optional[String], + @RequestParam(name = VASTAANOTTAJA_PARAM_NAME, required = false) vastaanottajanEmail: Optional[String], request: HttpServletRequest): ResponseEntity[PalautaLahetyksetResponse] = // TODO tarkempi käyttöoikeusrajaus/suodatus val securityOperaatiot = new SecurityOperaatiot val kantaOperaatiot = new KantaOperaatiot(DbUtil.database) - + val emailRegex = "^[^\\s,@]+@(([a-zA-Z\\-0-9])+\\.)+([a-zA-Z\\-0-9]){2,}$".r + LOG.info(s"vastaanottajan email: $vastaanottajanEmail") try Right(None) .flatMap(_ => @@ -145,20 +147,24 @@ class LahetysResource { .map(virheet => if (enintaan.isPresent && (enintaanInt.isEmpty || enintaanInt.get < LAHETYKSET_ENINTAAN_MIN || enintaanInt.get > LAHETYKSET_ENINTAAN_MAX)) virheet.appended(LAHETYKSET_ENINTAAN_INVALID) else virheet) + .map(virheet => + if (vastaanottajanEmail.isPresent && !emailRegex.matches(vastaanottajanEmail.get())) + virheet.appended(VASTAANOTTAJA_INVALID) else virheet) .get if (!virheet.isEmpty) Left(ResponseEntity.status(HttpStatus.BAD_REQUEST).body(PalautaLahetyksetFailureResponse(virheet.asJava))) else Right((alkaenAika, enintaanInt))) .flatMap((alkaenAika, enintaanInt) => - val lahetykset = kantaOperaatiot.getLahetykset(alkaenAika, enintaanInt, securityOperaatiot.getKayttajanOikeudet()) + val lahetykset = kantaOperaatiot.getLahetykset(alkaenAika, enintaanInt, securityOperaatiot.getKayttajanOikeudet(), vastaanottajanEmail.orElse("")) if (lahetykset.isEmpty) - Left(ResponseEntity.status(HttpStatus.GONE).build()) + // on ok tilanne että haku ei palauta tuloksia + Left(ResponseEntity.status(HttpStatus.OK).body(PalautaLahetyksetSuccessResponse(Seq.empty.asJava,Optional.empty))) else val lahetysStatukset = kantaOperaatiot.getLahetystenVastaanottotilat(lahetykset.map(_.tunniste)) val seuraavatAlkaen = { - if (lahetykset.isEmpty || kantaOperaatiot.getLahetykset(Option.apply(lahetykset.last.luotu), Option.apply(1), securityOperaatiot.getKayttajanOikeudet()).isEmpty) + if (lahetykset.isEmpty || kantaOperaatiot.getLahetykset(Option.apply(lahetykset.last.luotu), Option.apply(1), securityOperaatiot.getKayttajanOikeudet(), vastaanottajanEmail.orElse("")).isEmpty) Optional.empty else Optional.of(lahetykset.last.luotu.toString) 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 c49a4047..94947cea 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 @@ -22,6 +22,7 @@ object RaportointiAPIConstants { final val GET_VASTAANOTTAJAT_PATH = GET_LAHETYS_PATH + "/vastaanottajat" final val ALKAEN_PARAM_NAME = "alkaen" final val ENINTAAN_PARAM_NAME = "enintaan" + final val VASTAANOTTAJA_PARAM_NAME = "vastaanottaja" final val VIESTIT_PATH = VERSIONED_RAPORTOINTI_API_PREFIX + "/viestit" final val VIESTITUNNISTE_PARAM_NAME = "viestiTunniste" @@ -81,4 +82,5 @@ object RaportointiAPIConstants { final val ENINTAAN_INVALID = ENINTAAN_PARAM_NAME + "-parametri: Arvon pitää olla numero väliltä " + VASTAANOTTAJAT_ENINTAAN_MIN_STR + "-" + VASTAANOTTAJAT_ENINTAAN_MAX_STR final val ALKAEN_AIKA_TUNNISTE_INVALID = ALKAEN_PARAM_NAME + "-parametri: Tunniste ei ole muodoltaan validi aikaleima" final val LAHETYKSET_ENINTAAN_INVALID = ENINTAAN_PARAM_NAME + "-parametri: Arvon pitää olla numero väliltä " + LAHETYKSET_ENINTAAN_MIN_STR + "-" + LAHETYKSET_ENINTAAN_MAX_STR + final val VASTAANOTTAJA_INVALID = VASTAANOTTAJA_PARAM_NAME + "-parametri: Tunniste ei ole validi sähköposti" } 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 bd85921e..00c3487f 100644 --- a/shared/src/main/scala/fi/oph/viestinvalitys/business/KantaOperaatiot.scala +++ b/shared/src/main/scala/fi/oph/viestinvalitys/business/KantaOperaatiot.scala @@ -108,30 +108,45 @@ class KantaOperaatiot(db: JdbcBackend.JdbcDatabaseDef) { Lahetys(UUID.fromString(tunniste), otsikko, omistaja, lahettavapalvelu, Option.apply(lahettavanVirkailijanOid), Kontakti(Option.apply(lahettajanNimi), lahettajanSahkoposti), Option.apply(replyto), Prioriteetti.valueOf(prioriteetti), Instant.parse(luotu))) + private def isPaakayttaja(kayttooikeudet: Set[String]): Boolean = + kayttooikeudet("ROLE_VIESTINVALITYS_OPH_PAAKAYTTAJA") + /** * Palauttaa listan lähetyksiä hakuehdoilla rajattuna käyttöoikeuksien mukaan * - * @param alkaen aikaleima, jonka jälkeen luodut haetaan (sivutus) - * @param enintaan palautettavan lähetysjoukon maksimikoko (sivutus) - * + * @param alkaen aikaleima, jonka jälkeen luodut haetaan (sivutus) + * @param enintaan palautettavan lähetysjoukon maksimikoko (sivutus) * @return hakuehtoja (TODO) vastaavat lähetykset */ - def getLahetykset(alkaen: Option[Instant], enintaan: Option[Int], kayttooikeudet: Set[Kayttooikeus]): Seq[Lahetys] = - val lahetyksetQuery = sql""" - SELECT lahetykset.tunniste, otsikko, omistaja, lahettavapalvelu, lahettavanVirkailijanOid, lahettajanNimi, lahettajanSahkoposti, replyto, prioriteetti, to_json(luotu::timestamptz)#>>'{}' - FROM lahetykset - JOIN lahetykset_kayttooikeudet ON lahetykset_kayttooikeudet.lahetys_tunniste=lahetykset.tunniste - JOIN kayttooikeudet ON lahetykset_kayttooikeudet.kayttooikeus_tunniste=kayttooikeudet.tunniste - WHERE luotu<${alkaen.getOrElse(Instant.now()).toString}::timestamptz - AND kayttooikeudet.organisaatio || '_' || kayttooikeudet.oikeus IN (#${kayttooikeudet.map(oikeus => "'" + oikeus.organisaatio.get + "_" + oikeus.oikeus + "'").mkString(",")}) - GROUP BY lahetykset.tunniste - ORDER BY luotu DESC - LIMIT ${enintaan.getOrElse(256)} - """.as[(String, String, String, String, String, String, String, String, String, String)] + def getLahetykset(alkaen: Option[Instant], enintaan: Option[Int], kayttooikeudet: Set[Kayttooikeus], vastaanottajanEmail: String = ""): Seq[Lahetys] = + LOG.info(s"vastaanottajan email kantahaku: $vastaanottajanEmail") + + val selectLahetyksetSql = + """SELECT lahetykset.tunniste, lahetykset.otsikko, lahetykset.omistaja, lahettavapalvelu, lahettavanVirkailijanOid, lahettajanNimi, lahettajanSahkoposti, replyto, lahetykset.prioriteetti, to_json(lahetykset.luotu::timestamptz)#>>'{}' FROM lahetykset""" + val kayttooikeudetJoin = if isPaakayttaja(kayttooikeudet) then "" else + """ JOIN lahetykset_kayttooikeudet ON lahetykset_kayttooikeudet.lahetys_tunniste=lahetykset.tunniste JOIN kayttooikeudet ON lahetykset_kayttooikeudet.kayttooikeus_tunniste=kayttooikeudet.tunniste""" +// val kayttooikeudetWhere = if isPaakayttaja(kayttooikeudet) then "" else s""" AND kayttooikeus IN (${kayttooikeudet.map(oikeus => "'" + oikeus + "'").mkString(",")})""" + val kayttooikeudetWhere = if isPaakayttaja(kayttooikeudet) then "" else s"""AND kayttooikeudet.organisaatio || '_' || kayttooikeudet.oikeus IN (#${kayttooikeudet.map(oikeus => "'" + oikeus.organisaatio.get + "_" + oikeus.oikeus + "'").mkString(",")})""" + val vastaanottajatJoin = if vastaanottajanEmail.isEmpty() then "" + else " JOIN viestit ON lahetykset.tunniste=viestit.lahetys_tunniste JOIN vastaanottajat ON vastaanottajat.viesti_tunniste=viestit.tunniste " + val vastaanottajatWhere = if vastaanottajanEmail.isEmpty() then "" + else s" AND vastaanottajat.sahkopostiosoite ='$vastaanottajanEmail'" + + val lahetyksetQuery = sql"""#$selectLahetyksetSql + #$kayttooikeudetJoin + #$vastaanottajatJoin + WHERE lahetykset.luotu<${alkaen.getOrElse(Instant.now()).toString}::timestamptz + #$kayttooikeudetWhere + #$vastaanottajatWhere + GROUP BY lahetykset.tunniste + ORDER BY lahetykset.luotu DESC + LIMIT ${enintaan.getOrElse(256)} + """.as[(String, String, String, String, String, String, String, String, String, String)] + + Await.result(db.run(lahetyksetQuery), DB_TIMEOUT) + .map((tunniste, otsikko, omistaja, lahettavapalvelu, lahettavanVirkailijanOid, lahettajanNimi, lahettajanSahkoposti, replyTo, prioriteetti, luotu) => + Lahetys(UUID.fromString(tunniste), otsikko, omistaja, lahettavapalvelu, Option.apply(lahettavanVirkailijanOid), Kontakti(Option.apply(lahettajanNimi), lahettajanSahkoposti), Option.apply(replyTo), Prioriteetti.valueOf(prioriteetti), Instant.parse(luotu))) - Await.result(db.run(lahetyksetQuery), DB_TIMEOUT) - .map((tunniste, otsikko, omistaja, lahettavapalvelu, lahettavanVirkailijanOid, lahettajanNimi, lahettajanSahkoposti, replyTo, prioriteetti, luotu) => - Lahetys(UUID.fromString(tunniste), otsikko, omistaja, lahettavapalvelu, Option.apply(lahettavanVirkailijanOid), Kontakti(Option.apply(lahettajanNimi), lahettajanSahkoposti), Option.apply(replyTo), Prioriteetti.valueOf(prioriteetti), Instant.parse(luotu))) /** * Tallentaa uuden liitteen 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 b9ded7e1..59627f8a 100644 --- a/shared/src/test/scala/fi/oph/viestinvalitys/business/KantaOperaatiotTest.scala +++ b/shared/src/test/scala/fi/oph/viestinvalitys/business/KantaOperaatiotTest.scala @@ -146,6 +146,31 @@ class KantaOperaatiotTest { omistaja ) + private def tallennaRaataloityViesti(vastaanottajat: Seq[Kontakti], otsikko: String = "otsikko", sisalto: String = "sisältö", + lahetysTunniste: UUID = null, + kayttoOikeudet: Set[String] = Set("ROLE_JARJESTELMA_OIKEUS1", "ROLE_JARJESTELMA_OIKEUS2"), + omistaja: String = "omistaja", + lahettavaPalvelu: String = "palvelu", + maskit: Map[String, Option[String]] = Map.empty) = + kantaOperaatiot.tallennaViesti( + otsikko, + sisalto, + SisallonTyyppi.TEXT, + Set(Kieli.FI), + maskit, + Option.empty, + Option.apply(Kontakti(Option.apply("Lasse Lahettaja"), "lasse.lahettaja@oph.fi")), + Option.empty, + vastaanottajat, + Seq.empty, + Option.apply(lahettavaPalvelu), + Option.apply(lahetysTunniste), + Option.apply(Prioriteetti.NORMAALI), + Option.apply(10), + kayttoOikeudet, + Map("avain" -> Seq("arvo")), + omistaja + ) /** * Testataan lähetyksien tallennus ja luku */ @@ -423,6 +448,11 @@ class KantaOperaatiotTest { Set(Kayttooikeus(Option.apply("JARJESTELMA"), "OIKEUS1"))) Assertions.assertEquals(1, lahetyksetSivutus.size) Assertions.assertEquals(lahetyksetSivutus.head.luotu,lahetys1.luotu) + // haku vastaanottajan spostilla + tallennaRaataloityViesti(Seq.apply(Kontakti(Option.apply("Testi vastaanottaja"),"testi.vastaanottaja@example.org")), lahetysTunniste = lahetys4.tunniste, kayttoOikeudet = Set("ROLE_JARJESTELMA_OIKEUS1")) + Assertions.assertEquals(1, kantaOperaatiot.getLahetykset(Option.empty,Option.empty, Set("ROLE_JARJESTELMA_OIKEUS1"),"testi.vastaanottaja@example.org").size) + Assertions.assertEquals(0, kantaOperaatiot.getLahetykset(Option.empty,Option.empty, Set("ROLE_JARJESTELMA_OIKEUS2"),"testi.vastaanottaja@example.org").size) + Assertions.assertEquals(0, kantaOperaatiot.getLahetykset(Option.empty,Option.empty, Set("ROLE_JARJESTELMA_OIKEUS1"),"test.vastaanottaja@example.org").size) /** * Testataan että viestiin voi liittää erikseen luodun lähetykset diff --git a/viestinvalitys-raportointi/README.md b/viestinvalitys-raportointi/README.md index 642b1e6d..d3b24781 100644 --- a/viestinvalitys-raportointi/README.md +++ b/viestinvalitys-raportointi/README.md @@ -13,7 +13,7 @@ ja löytyy osoitteesta [http://localhost:3000](http://localhost:3000) Lokaaliympäristössä palvelu toimii lokaalia viestinvälityspalvelua vasten, ks. https://github.com/Opetushallitus/viestinvalityspalvelu -TODO lokaalikehitys testiympäristön viestintäpalvelua vasten +TODO prod buildin ajaminen lokaalisti, lokaalikehitys testiympäristön CASia ja viestintäpalvelua vasten ## Yksikkötestien ajo @@ -29,11 +29,14 @@ Sovellus on toteutettu [Next.js](https://nextjs.org/) -frameworkilla ja luotu [` Käyttöliittymäkomponenteissa on käytetty [Material UI](https://mui.com/material-ui/getting-started/) -kirjastoa -## FYI +## Asennusympäristö + +Sovellus paketoidaan "standalone"-muodossa https://nextjs.org/docs/app/api-reference/next-config-js/output#automatically-copying-traced-files -Bugin vuoksi juurihakemistossa täytyy olla tyhjä /pages -hakemisto, jotta middleware tulee ajettua lokaalikehitysmoodissa. Ks. https://github.com/vercel/next.js/issues/43141 +OPH:n ympäristöissä sovellus toimii aws-lambdoissa nodejs-ajoympäristössä, mikä on toistaiseksi Nextjs:n oletusajoympäristö. Tietyt Nextjs-dokumentaatiossa mainitut ominaisuudet kuten middlewaret ovat käytettävissä vain edge-ajoympäristössä, ks. https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime +Tämän vuoksi esim. http-otsakkeiden ja evästeiden käsittely täytyy tehdä middlewaren sijaan palvelinkomponenteissa. -Tämän korjaantumista kannattanee seurailla nextjs-versiopäivityksissä ja poistaa turha hakemisto kun issue on korjattu. +## FYI Jotta lokaaliympäristössä voi käyttää https-apeja self signed sertifikaatilla, täytyy package.jsonin dev-käynnistysasetuksissa olla tämä konfiguraatio: diff --git a/viestinvalitys-raportointi/next.config.js b/viestinvalitys-raportointi/next.config.js index de206d94..bd1d0640 100644 --- a/viestinvalitys-raportointi/next.config.js +++ b/viestinvalitys-raportointi/next.config.js @@ -1,9 +1,36 @@ /** @type {import('next').NextConfig} */ + +const cspHeader = ` + default-src 'self'; + script-src 'self' 'unsafe-eval' 'unsafe-inline'; + style-src 'self' 'unsafe-inline'; + img-src 'self' blob: data:; + font-src 'self'; + object-src 'none'; + base-uri 'self'; + form-action 'self'; + frame-ancestors 'none'; + block-all-mixed-content; + upgrade-insecure-requests; +` const isProd = process.env.NODE_ENV === 'production' const nextConfig = { output: 'standalone', basePath: isProd ? '/raportointi' : undefined, assetPrefix: isProd? '/static' : undefined, + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'Content-Security-Policy', + value: cspHeader.replace(/\n/g, ''), + }, + ], + }, + ] + }, } module.exports = nextConfig diff --git a/viestinvalitys-raportointi/src/app/Haku.tsx b/viestinvalitys-raportointi/src/app/Haku.tsx index 9404129f..06d53c81 100644 --- a/viestinvalitys-raportointi/src/app/Haku.tsx +++ b/viestinvalitys-raportointi/src/app/Haku.tsx @@ -1,5 +1,5 @@ 'use client'; -import { FormControl, InputLabel, MenuItem, Select, TextField } from '@mui/material'; +import { FormControl, FormLabel, InputLabel, MenuItem, Select, TextField } from '@mui/material'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { useDebouncedCallback } from 'use-debounce'; import { useCallback } from 'react'; @@ -14,6 +14,8 @@ export default function Haku() { // lisätään hakuparametreihin uusi key-value-pari const createQueryString = useCallback( (name: string, value: any) => { + console.log('name:', name) + console.log('value:', value) const params = new URLSearchParams(searchParams?.toString() || '') params.set(name, value) @@ -22,7 +24,7 @@ export default function Haku() { [searchParams] ) - // päivitetään 3s viiveellä hakuparametrit + // päivitetään 10s viiveellä hakuparametrit const handleTypedSearch = useDebouncedCallback((term) => { console.log(`Searching... ${term}`); const params = new URLSearchParams(searchParams?.toString() || ''); @@ -32,15 +34,14 @@ export default function Haku() { params.delete('hakusana'); } replace(`${pathname}?${params.toString()}`); - }, 300); + }, 3000); return ( - Mistä haetaan + Mistä haetaan + Hae viestejä { diff --git a/viestinvalitys-raportointi/src/app/Lahetykset.tsx b/viestinvalitys-raportointi/src/app/Lahetykset.tsx index c1b2bbc0..9452bf37 100644 --- a/viestinvalitys-raportointi/src/app/Lahetykset.tsx +++ b/viestinvalitys-raportointi/src/app/Lahetykset.tsx @@ -1,48 +1,61 @@ 'use client' import { Link as MuiLink, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'; // importoidaan MUI Link ja Nextjs Link komponentit eri nimillä +import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined'; import NextLink from 'next/link'; import { Lahetys } from './lib/types'; -import LocalDateTime from './LocalDateTime'; -import LahetysStatus from './LahetysStatus'; +import LocalDateTime from './components/LocalDateTime'; +import LahetysStatus from './components/LahetysStatus'; import { lahetyksenStatus } from './lib/util'; + const LahetyksetTable = ({lahetykset}: {lahetykset: Lahetys[]}) => { + return ( + + + + + Luotu + Lähettäjän nimi + Lähettävä palvelu + Otsikko + Tilat + + + + {lahetykset + .map((row) => ( + + + {row.lahettajanNimi} + + + {row.otsikko} + + + {row.lahettavaPalvelu} + {lahetyksenStatus(row.tilat)} + + ))} + +
+
+ )} + const Lahetykset = ({lahetykset}: {lahetykset: Lahetys[]}) => { + return ( - - - - - Luotu - Lähettäjän nimi - Lähettävä palvelu - Otsikko - Tilat - - - - {lahetykset - .map((row) => ( - - - {row.lahettajanNimi} - - - {row.otsikko} - - - {row.lahettavaPalvelu} - {lahetyksenStatus(row.tilat)} - - ))} - -
-
-
+ {lahetykset.length > 0 + ? + :
+ +

Hakuehdoilla ei löytynyt tuloksia

+
} + + )} export default Lahetykset diff --git a/viestinvalitys-raportointi/src/app/LahetyksetSivutus.tsx b/viestinvalitys-raportointi/src/app/LahetyksetSivutus.tsx index 30a681dd..1f56a994 100644 --- a/viestinvalitys-raportointi/src/app/LahetyksetSivutus.tsx +++ b/viestinvalitys-raportointi/src/app/LahetyksetSivutus.tsx @@ -22,7 +22,7 @@ const LahetyksetSivutus = ({ seuraavatAlkaen }: { seuraavatAlkaen?: string }) => return ( seuraavatAlkaen ? - + Seuraavat : <> diff --git a/viestinvalitys-raportointi/src/app/LahetysStatus.tsx b/viestinvalitys-raportointi/src/app/components/LahetysStatus.tsx similarity index 83% rename from viestinvalitys-raportointi/src/app/LahetysStatus.tsx rename to viestinvalitys-raportointi/src/app/components/LahetysStatus.tsx index 6bbefbdb..0ff89f7a 100644 --- a/viestinvalitys-raportointi/src/app/LahetysStatus.tsx +++ b/viestinvalitys-raportointi/src/app/components/LahetysStatus.tsx @@ -1,7 +1,7 @@ 'use client' import { CheckCircle, Error, Warning, WatchLater } from '@mui/icons-material'; -import { Status, VastaanottajaTila } from "./lib/types" -import { getLahetysStatus } from './lib/util'; +import { Status, VastaanottajaTila } from "../lib/types" +import { getLahetysStatus } from '../lib/util'; const LahetysStatus = ({tilat}: {tilat: VastaanottajaTila[]}) => { diff --git a/viestinvalitys-raportointi/src/app/LocalDateTime.tsx b/viestinvalitys-raportointi/src/app/components/LocalDateTime.tsx similarity index 90% rename from viestinvalitys-raportointi/src/app/LocalDateTime.tsx rename to viestinvalitys-raportointi/src/app/components/LocalDateTime.tsx index d3653828..6ebcbb72 100644 --- a/viestinvalitys-raportointi/src/app/LocalDateTime.tsx +++ b/viestinvalitys-raportointi/src/app/components/LocalDateTime.tsx @@ -1,7 +1,7 @@ 'use client' import { Suspense } from 'react' -import { useHydration } from './hooks/useHydration' +import { useHydration } from '../hooks/useHydration' // pieni kikkailu SSR hydration-ongelman välttämiseksi // ks. https://francoisbest.com/posts/2023/displaying-local-times-in-nextjs diff --git a/viestinvalitys-raportointi/src/app/components/VirheAlert.tsx b/viestinvalitys-raportointi/src/app/components/VirheAlert.tsx new file mode 100644 index 00000000..592c53f5 --- /dev/null +++ b/viestinvalitys-raportointi/src/app/components/VirheAlert.tsx @@ -0,0 +1,14 @@ +'use client' +import { Alert, Box } from "@mui/material" + +const VirheAlert = ({ virheet }: { virheet?: string[] }) => { + return ( + virheet ? + + {virheet.map((virhe: string, index) => {virhe})} + + : <> + ) + } + + export default VirheAlert \ No newline at end of file diff --git a/viestinvalitys-raportointi/src/app/error.tsx b/viestinvalitys-raportointi/src/app/error.tsx index 19b595c9..86be9693 100644 --- a/viestinvalitys-raportointi/src/app/error.tsx +++ b/viestinvalitys-raportointi/src/app/error.tsx @@ -17,7 +17,6 @@ export default function Error({

Tapahtui virhe

Error message: {error.message}

-

Error digest: {error.digest || ''}