diff --git a/src/components/notification/Notification.tsx b/src/components/notification/Notification.tsx index bf1302b89..c8dd5d473 100644 --- a/src/components/notification/Notification.tsx +++ b/src/components/notification/Notification.tsx @@ -7,6 +7,7 @@ import classNames from "classnames"; export enum NotificationType { DEFAULT = "default", INFO = "info", + INFO_GRAY = "info_gray", WARN = "warn", ERROR = "error", } @@ -14,6 +15,7 @@ export enum NotificationType { const defaultIcons = new Map([ [NotificationType.DEFAULT, undefined], [NotificationType.INFO, "info-circle"], + [NotificationType.INFO_GRAY, "info-circle"], [NotificationType.WARN, "exclamation-circle"], [NotificationType.ERROR, "exclamation-triangle"], ]); diff --git a/src/components/projekti/ProjektiSideNavigation.tsx b/src/components/projekti/ProjektiSideNavigation.tsx index 0d11dae1e..a022fc757 100644 --- a/src/components/projekti/ProjektiSideNavigation.tsx +++ b/src/components/projekti/ProjektiSideNavigation.tsx @@ -30,7 +30,11 @@ export default function ProjektiSideNavigation(): ReactElement { href: oid && `/yllapito/projekti/${oid}/aloituskuulutus`, disabled: !projekti?.status || projekti?.status === Status.EI_JULKAISTU, }, - { title: "Suunnitteluvaihe", href: oid && `/yllapito/projekti/${oid}/suunnittelu`, disabled: true }, + { + title: "Suunnitteluvaihe", + href: oid && `/yllapito/projekti/${oid}/suunnittelu`, + disabled: !projekti?.status || !projekti?.aloitusKuulutusJulkaisut, + }, { title: "Nähtävilläolovaihe", href: oid && `/yllapito/projekti/${oid}/nahtavillaolo`, disabled: true }, ]; return ( diff --git a/src/components/projekti/aloituskuulutus/AloituskuulutusRO.tsx b/src/components/projekti/aloituskuulutus/AloituskuulutusRO.tsx new file mode 100644 index 000000000..bf0aa1b80 --- /dev/null +++ b/src/components/projekti/aloituskuulutus/AloituskuulutusRO.tsx @@ -0,0 +1,155 @@ +import { AloitusKuulutusJulkaisu, AloitusKuulutusTila, Kieli } from "@services/api"; +import React, { ReactElement, useRef } from "react"; +import Notification, { NotificationType } from "@components/notification/Notification"; +import { capitalize, replace } from "lodash"; +import Button from "@components/button/Button"; +import log from "loglevel"; + +interface Props { + oid?: string; + aloituskuulutusjulkaisu?: AloitusKuulutusJulkaisu | null; +} + +export default function AloituskuulutusRO({ aloituskuulutusjulkaisu, oid }: Props): ReactElement { + const pdfFormRef = useRef(null); + + const muotoilePvm = (pvm: string | null | undefined) => { + if (!pvm) { + return; + } + return new Date(pvm).toLocaleDateString("fi"); + }; + + const naytaEsikatselu = async (action: string, kieli: Kieli | undefined | null) => { + log.info("Näytä esikatselu ", kieli); + if (!action) return; + + if (pdfFormRef.current) { + pdfFormRef.current.action = action; + pdfFormRef.current?.submit(); + } + }; + + return ( + <> + {aloituskuulutusjulkaisu?.tila === AloitusKuulutusTila.ODOTTAA_HYVAKSYNTAA && ( + + Aloituskuulutus on hyväksyttävänä projektipäälliköllä. Jos kuulutusta tarvitsee muokata, ota yhteys + projektipäällikköön. + + )} + {aloituskuulutusjulkaisu?.suunnitteluSopimus && ( + + Hankkeesta on tehty suunnittelusopimus kunnan kanssa +
+
+ {capitalize(aloituskuulutusjulkaisu?.suunnitteluSopimus.kunta)} +
+ {capitalize(aloituskuulutusjulkaisu.suunnitteluSopimus.etunimi)}{" "} + {capitalize(aloituskuulutusjulkaisu.suunnitteluSopimus.sukunimi)}, puh.{" "} + {aloituskuulutusjulkaisu.suunnitteluSopimus.puhelinnumero},{" "} + {aloituskuulutusjulkaisu.suunnitteluSopimus.email + ? replace(aloituskuulutusjulkaisu.suunnitteluSopimus.email, "@", "[at]") + : ""} +
+ )} +
+

Kuulutuspäivä

+

Kuulutusvaihe päättyy

+

{muotoilePvm(aloituskuulutusjulkaisu?.kuulutusPaiva)}

+

{muotoilePvm(aloituskuulutusjulkaisu?.siirtyySuunnitteluVaiheeseen)}

+
+
+

Kuulutuksessa esitettävät yhteystiedot

+ {aloituskuulutusjulkaisu?.yhteystiedot?.map((yhteistieto, index) => ( +

+ {capitalize(yhteistieto?.etunimi)} {capitalize(yhteistieto?.sukunimi)}, puh. {yhteistieto?.puhelinnumero},{" "} + {yhteistieto?.sahkoposti ? replace(yhteistieto?.sahkoposti, "@", "[at]") : ""} +

+ ))} +
+
+

+ Tiivistetty hankkeen kuvaus ensisijaisella kielellä ({aloituskuulutusjulkaisu?.kielitiedot?.ensisijainenKieli} + ) +

+

+ {aloituskuulutusjulkaisu?.kielitiedot?.ensisijainenKieli === Kieli.SUOMI + ? aloituskuulutusjulkaisu?.hankkeenKuvaus + : aloituskuulutusjulkaisu?.hankkeenKuvausRuotsi} +

+
+ {aloituskuulutusjulkaisu?.kielitiedot?.toissijainenKieli && ( +
+

+ Tiivistetty hankkeen kuvaus toissijaisella kielellä ( + {aloituskuulutusjulkaisu?.kielitiedot?.toissijainenKieli}) +

+

+ {aloituskuulutusjulkaisu?.kielitiedot?.toissijainenKieli === Kieli.SUOMI + ? aloituskuulutusjulkaisu?.hankkeenKuvaus + : aloituskuulutusjulkaisu?.hankkeenKuvausRuotsi} +

+
+ )} +
+

Esikatseltavat tiedostot

+

Kuulutus ja ilmoitus ensisijaisella kielellä ({aloituskuulutusjulkaisu?.kielitiedot?.ensisijainenKieli})

+
+ + +
+
+
+

Kuulutus ja ilmoitus toissijaisella kielellä ({aloituskuulutusjulkaisu?.kielitiedot?.toissijainenKieli})

+
+ + +
+
+
+ +
+ + ); +} diff --git a/src/pages/yllapito/projekti/[oid]/aloituskuulutus.tsx b/src/pages/yllapito/projekti/[oid]/aloituskuulutus.tsx index 5d506d8b5..e3717012f 100644 --- a/src/pages/yllapito/projekti/[oid]/aloituskuulutus.tsx +++ b/src/pages/yllapito/projekti/[oid]/aloituskuulutus.tsx @@ -3,24 +3,32 @@ import ProjektiPageLayout from "@components/projekti/ProjektiPageLayout"; import { useRouter } from "next/router"; import React, { ReactElement, useEffect, useRef, useState } from "react"; import useProjekti from "src/hooks/useProjekti"; -import * as Yup from "yup"; -import { SchemaOf } from "yup"; -import { useForm, UseFormProps } from "react-hook-form"; +import { FormProvider, useForm, UseFormProps } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import Button from "@components/button/Button"; import Notification, { NotificationType } from "@components/notification/Notification"; -import { AloitusKuulutusInput, api, LaskuriTyyppi, Projekti, Status, TallennaProjektiInput } from "@services/api"; +import { + AloitusKuulutusInput, + AloitusKuulutusTila, + api, + LaskuriTyyppi, + Projekti, + Status, + TallennaProjektiInput, + AloitusKuulutusJulkaisu, + TilasiirtymaToiminto, +} from "@services/api"; import log from "loglevel"; import { PageProps } from "@pages/_app"; import DatePicker from "@components/form/DatePicker"; import { getProjektiValidationSchema, ProjektiTestType } from "src/schemas/projekti"; import ProjektiErrorNotification from "@components/projekti/ProjektiErrorNotification"; import KuulutuksenYhteystiedot from "@components/projekti/aloituskuulutus/KuulutuksenYhteystiedot"; -import { kayttoOikeudetSchema } from "src/schemas/kayttoOikeudet"; -import { puhelinNumeroSchema } from "src/schemas/puhelinNumero"; import deleteFieldArrayIds from "src/util/deleteFieldArrayIds"; -import cloneDeep from "lodash/cloneDeep"; +import { cloneDeep, find } from "lodash"; import useSnackbars from "src/hooks/useSnackbars"; +import { aloituskuulutusSchema } from "src/schemas/aloituskuulutus"; +import AloituskuulutusRO from "@components/projekti/aloituskuulutus/AloituskuulutusRO"; type ProjektiFields = Pick; type RequiredProjektiFields = Required<{ @@ -36,94 +44,6 @@ type FormValues = RequiredProjektiFields & { const maxAloituskuulutusLength = 2000; -const draftValidationSchema: SchemaOf = Yup.object().shape({ - oid: Yup.string().required(), - kayttoOikeudet: kayttoOikeudetSchema, - aloitusKuulutus: Yup.object().shape({ - hankkeenKuvaus: Yup.string().max( - maxAloituskuulutusLength, - `Aloituskuulutukseen voidaan kirjoittaa maksimissaan ${maxAloituskuulutusLength} merkkiä` - ), - kuulutusPaiva: Yup.string() - .test("is-valid-date", "Virheellinen päivämäärä", (dateString) => { - // KuulutusPaiva is not required when saved as a draft. - // This test doesn't throw errors if date is not set. - if (!dateString) { - return true; - } - let validDate = false; - try { - const dateString2 = new Date(dateString!).toISOString().split("T")[0]; - if (dateString2 === dateString) { - validDate = true; - } - } catch { - validDate = false; - } - return validDate; - }) - .test("not-in-past", "Aloituskuulutusta ei voida asettaa menneisyyteen", (dateString) => { - // KuulutusPaiva is not required when saved as a draft. - // This test doesn't throw errors if date is not set. - if (!dateString) { - return true; - } - const todayISODate = new Date().toISOString().split("T")[0]; - return dateString >= todayISODate; - }), - siirtyySuunnitteluVaiheeseen: Yup.string().test("is-valid-date", "Virheellinen päivämäärä", (dateString) => { - // KuulutusPaiva is not required when saved as a draft. - // This test doesn't throw errors if date is not set. - if (!dateString) { - return true; - } - let validDate = false; - try { - const dateString2 = new Date(dateString!).toISOString().split("T")[0]; - if (dateString2 === dateString) { - validDate = true; - } - } catch { - validDate = false; - } - return validDate; - }), - esitettavatYhteystiedot: Yup.array() - .notRequired() - .of( - Yup.object() - .shape({ - etunimi: Yup.string().required("Etunimi on pakollinen"), - sukunimi: Yup.string().required("Sukunimi on pakollinen"), - puhelinnumero: puhelinNumeroSchema.test( - "puhelinnumero-not-in-kayttoOikeudet", - "Tieto löytyy projektin henkilöistä. Valitse henkilö projektiin tallennettujen listasta", - function (puhelinnumero) { - const projekti = this.options.context as Projekti; - return !projekti?.kayttoOikeudet?.some( - (kayttaja) => kayttaja.puhelinnumero && kayttaja.puhelinnumero === puhelinnumero - ); - } - ), - sahkoposti: Yup.string() - .required("Sähköpostiosoite on pakollinen") - .email("Virheellinen sähköpostiosoite") - .test( - "sahkoposti-not-in-kayttoOikeudet", - "Tieto löytyy projektin henkilöistä. Valitse henkilö projektiin tallennettujen listasta", - function (sahkoposti) { - const projekti = this.options.context as Projekti; - return !projekti?.kayttoOikeudet?.some((kayttaja) => kayttaja.email && kayttaja.email === sahkoposti); - } - ), - organisaatio: Yup.string().required("Organisaatio on pakollinen"), - id: Yup.string().nullable().notRequired(), - }) - .nullable() - ), - }), -}); - const loadedProjektiValidationSchema = getProjektiValidationSchema([ ProjektiTestType.PROJEKTI_IS_LOADED, ProjektiTestType.PROJEKTI_HAS_PAALLIKKO, @@ -164,7 +84,7 @@ export default function Aloituskuulutus({ setRouteLabels }: PageProps): ReactEle }, [router.isReady, oid, projekti, setRouteLabels]); const formOptions: UseFormProps = { - resolver: yupResolver(draftValidationSchema, { abortEarly: false, recursive: true }), + resolver: yupResolver(aloituskuulutusSchema, { abortEarly: false, recursive: true }), defaultValues: { aloitusKuulutus: { hankkeenKuvaus: "" } }, mode: "onChange", reValidateMode: "onChange", @@ -175,7 +95,7 @@ export default function Aloituskuulutus({ setRouteLabels }: PageProps): ReactEle const { register, handleSubmit, - formState: { errors }, + formState: { errors, isDirty }, reset, watch, setValue, @@ -213,13 +133,24 @@ export default function Aloituskuulutus({ setRouteLabels }: PageProps): ReactEle } }, [projekti, reset]); - const saveDraft = async (formData: FormValues) => { + const getAloituskuulutusjulkaisuByTila = (tila: AloitusKuulutusTila): AloitusKuulutusJulkaisu | undefined => { + if (!projekti?.aloitusKuulutusJulkaisut) return undefined; + return find(projekti.aloitusKuulutusJulkaisut, (julkaisu) => { + return julkaisu.tila === tila; + }); + }; + + const saveAloituskuulutus = async (formData: FormValues) => { deleteFieldArrayIds(formData?.aloitusKuulutus?.esitettavatYhteystiedot); - setIsFormSubmitting(true); log.log("formData", formData); + await api.tallennaProjekti(formData); + await reloadProjekti(); + }; + + const saveDraft = async (formData: FormValues) => { + setIsFormSubmitting(true); try { - await api.tallennaProjekti(formData); - await reloadProjekti(); + await saveAloituskuulutus(formData); showSuccessMessage("Tallennus onnistui!"); } catch (e) { log.log("OnSubmit Error", e); @@ -228,9 +159,23 @@ export default function Aloituskuulutus({ setRouteLabels }: PageProps): ReactEle setIsFormSubmitting(false); }; - const sendToManager = async (formData: FormValues) => { - log.log(formData); - showInfoMessage("Lähetetään projektipäällikölle..."); + const sendToManager = async () => { + if (!projekti) return; + setIsFormSubmitting(true); + try { + if (isDirty) { + // should we even allow user to try sending to manager if form is dirty? + // await saveAloituskuulutus(formData); + // don't show succes toast we still want to send it to manager + } + await api.siirraTila({ oid: projekti.oid, toiminto: TilasiirtymaToiminto.LAHETA_HYVAKSYTTAVAKSI }); + await reloadProjekti(); + showSuccessMessage("Lähetys onnistui"); + } catch (error) { + log.error("", error); + showErrorMessage("Lähetyksessä tapahtui virhe"); + } + setIsFormSubmitting(false); }; const showPDFPreview = (formData: FormValues, action: string) => { @@ -243,7 +188,7 @@ export default function Aloituskuulutus({ setRouteLabels }: PageProps): ReactEle } }; - const { showSuccessMessage, showErrorMessage, showInfoMessage } = useSnackbars(); + const { showSuccessMessage, showErrorMessage } = useSnackbars(); const getPaattymispaiva = async (value: string) => { try { @@ -257,107 +202,120 @@ export default function Aloituskuulutus({ setRouteLabels }: PageProps): ReactEle return ( -
-
- - - Aloituskuulutusta ei ole vielä julkaistu palvelun julkisella puolella.{" "} - {watchKuulutusPaiva && !errors.aloitusKuulutus?.kuulutusPaiva - ? `Kuulutuspäivä on ${new Date(watchKuulutusPaiva).toLocaleDateString("fi")}` - : "Kuulutuspäivää ei ole asetettu"} - . Voit edelleen tehdä muutoksia projektin tietoihin. Tallennetut muutokset huomioidaan kuulutuksessa. - -

Suunnittelun aloittamisesta kuuluttaminen

-

- Kun suunnitelman aloittamisesta kuulutetaan, projektista julkaistaan aloituskuulutustiedot tämän palvelun - julkisella puolella. Aloituskuulutuksen näkyvilläoloaika määräytyy annetun kuulutuspäivän mukaan. Projekti - siirtyy määräajan jälkeen automaattisesti suunnitteluvaiheeseen. -

- -
-

Ohjeet

-
    -
  • - Anna päivämäärä, jolloin suunnittelun aloittamisesta kuulutetaan tämän palvelun julkisella puolella. -
  • -
  • - Kuvaa aloituskuulutuksessa esitettävään sisällönkuvauskenttään lyhyesti suunnittelukohteen alueellinen - rajaus (maantiealue ja vaikutusalue), suunnittelun tavoitteet, vaikutukset ja toimenpiteet - pääpiirteittäin karkealla tasolla. Älä lisää tekstiin linkkejä. -
  • -
-
-
-
- { - getPaattymispaiva(event.target.value); - }} - /> - -
-
- -
-