Skip to content

Commit

Permalink
feat: HASSU-740 nahtävillaolo sisällonkuvaus ja yhteystiedot (#264)
Browse files Browse the repository at this point in the history
* UI tehty niin pitkälle kuin lman BE:tä mahdollista.

* Toteutus nähtävillaolon päivämäärien tallentamiselle.

* Hankkeen kuvaus UI nahtävilläolovaiheessa

* Validoi nähtävilläolokuulutuksen sisällönkuvaus

* Poista console.log

* Lisää validointia vuorovaikutukseen

* Toteuta ilmoituksessa näytettävien henkilöiden lisäämisen ui ja korjaa nähtävilläolovaiheen lomaketta.

* Siivoa mergesotkua
  • Loading branch information
ValheKouneli authored Jun 13, 2022
1 parent f2a71cd commit fca1999
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,59 @@
import Textarea from "@components/form/Textarea";
import Section from "@components/layout/Section";
import SectionContent from "@components/layout/SectionContent";
import { HankkeenKuvauksetInput, Kielitiedot, Kieli } from "@services/api";
import React from "react";
import { useFormContext } from "react-hook-form";
import lowerCase from "lodash/lowerCase";

type Props = {};
type Props = {
kielitiedot: Kielitiedot | null | undefined;
};

type FormFields = {
nahtavillaoloVaihe: {
hankkeenKuvaus: HankkeenKuvauksetInput;
};
};

export default function KuulutusJaJulkaisuPaiva({ kielitiedot }: Props) {
const {
register,
formState: { errors },
} = useFormContext<FormFields>();

const ensisijainenKieli = kielitiedot?.ensisijainenKieli || Kieli.SUOMI;
const toissijainenKieli = kielitiedot?.toissijainenKieli;

export default function HankkeenSisallonKuvaus({}: Props) {
return (
<Section>
<h4 className="vayla-small-title">Kuulutus ja julkaisupäivä</h4>
<p>
Kansalaisten tulee muistututtaa suunnitelmista järjestelmän kautta viimeistään alla olevana päivämääränä.
Muistutusten päivämäärä määräytyy kuulutuksen nähtävilläoloajan mukaan ja sitä ei voi muokata.
</p>
<SectionContent>
<h4 className="vayla-small-title">Hankkeen sisällönkuvaus</h4>
<p>
Kirjoita nähtäville asettamisen kuulutusta varten tiivistetty sisällönkuvaus hankkeesta. Kuvauksen on hyvä
sisältää esimerkiksi tieto suunnittelukohteen alueellista rajauksesta (maantietoalue ja vaikutusalue),
suunnittelun tavoitteet, vaikutukset ja toimenpiteet pääpiirteittäin karkealla tasolla. Älä lisää tekstiin
linkkejä.
</p>
</SectionContent>
<SectionContent>
<Textarea
label={`Tiivistetty hankkeen sisällönkuvaus ensisijaisella kielellä (${lowerCase(ensisijainenKieli)}) *`}
{...register(`nahtavillaoloVaihe.hankkeenKuvaus.${ensisijainenKieli}`)}
error={(errors.nahtavillaoloVaihe?.hankkeenKuvaus as any)?.[ensisijainenKieli]}
maxLength={2000}
/>
</SectionContent>
{toissijainenKieli && (
<SectionContent>
<Textarea
label={`Tiivistetty hankkeen sisällönkuvaus toissijaisella kielellä (${lowerCase(toissijainenKieli)}) *`}
{...register(`nahtavillaoloVaihe.hankkeenKuvaus.${toissijainenKieli}`)}
error={(errors.nahtavillaoloVaihe?.hankkeenKuvaus as any)?.[toissijainenKieli]}
maxLength={2000}
/>
</SectionContent>
)}
</Section>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useEffect } from "react";
import { UseFormProps, useForm, FormProvider } from "react-hook-form";
import { useProjektiRoute } from "src/hooks/useProjektiRoute";
import { nahtavillaoloKuulutusSchema } from "src/schemas/nahtavillaoloKuulutus";
import { removeTypeName } from "src/util/removeTypeName";
import NahtavillaoloPainikkeet from "../NahtavillaoloPainikkeet";
import HankkeenSisallonKuvaus from "./HankkeenSisallonKuvaus";
import KuulutuksenJaIlmoituksenEsikatselu from "./KuulutuksenJaIlmoituksenEsikatselu";
Expand All @@ -17,8 +18,6 @@ export type KuulutuksenTiedotFormValues = Pick<TallennaProjektiInput, "oid" | "n
export default function KuulutuksenTiedot({}: Props) {
const { data: projekti } = useProjektiRoute();

console.log("plop", projekti);

const formOptions: UseFormProps<KuulutuksenTiedotFormValues> = {
resolver: yupResolver(nahtavillaoloKuulutusSchema, { abortEarly: false, recursive: true }),
mode: "onChange",
Expand All @@ -29,6 +28,16 @@ export default function KuulutuksenTiedot({}: Props) {
kuulutusPaiva: projekti?.nahtavillaoloVaihe?.kuulutusPaiva,
kuulutusVaihePaattyyPaiva: projekti?.nahtavillaoloVaihe?.kuulutusVaihePaattyyPaiva,
muistutusoikeusPaattyyPaiva: projekti?.nahtavillaoloVaihe?.muistutusoikeusPaattyyPaiva,
hankkeenKuvaus: removeTypeName(projekti?.nahtavillaoloVaihe?.hankkeenKuvaus),
kuulutusYhteystiedot: projekti?.nahtavillaoloVaihe?.kuulutusYhteystiedot
? projekti.nahtavillaoloVaihe.kuulutusYhteystiedot.map((yhteystieto) => removeTypeName(yhteystieto))
: [],
kuulutusYhteysHenkilot:
projekti?.kayttoOikeudet
?.filter(({ kayttajatunnus }) =>
projekti?.nahtavillaoloVaihe?.kuulutusYhteysHenkilot?.includes(kayttajatunnus)
)
.map(({ kayttajatunnus }) => kayttajatunnus) || [],
},
},
};
Expand All @@ -49,7 +58,7 @@ export default function KuulutuksenTiedot({}: Props) {
<FormProvider {...useFormReturn}>
<form>
<KuulutusJaJulkaisuPaiva />
<HankkeenSisallonKuvaus />
<HankkeenSisallonKuvaus kielitiedot={projekti?.kielitiedot} />
<KuulutuksessaEsitettavatYhteystiedot />
<KuulutuksenJaIlmoituksenEsikatselu />
<NahtavillaoloPainikkeet />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,205 @@
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import SectionContent from "@components/layout/SectionContent";
import { ProjektiRooli, YhteystietoInput, ProjektiKayttaja, NahtavillaoloVaiheTila } from "@services/api";
import Section from "@components/layout/Section";
import React from "react";
import { ReactElement, Fragment } from "react";
import Button from "@components/button/Button";
import HassuStack from "@components/layout/HassuStack";
import CheckBox from "@components/form/CheckBox";
import FormGroup from "@components/form/FormGroup";
import TextInput from "@components/form/TextInput";
import HassuGrid from "@components/HassuGrid";
import { maxPhoneLength } from "src/schemas/puhelinNumero";
import IconButton from "@components/button/IconButton";
import capitalize from "lodash/capitalize";
import replace from "lodash/replace";
import { useProjektiRoute } from "src/hooks/useProjektiRoute";
import { KuulutuksenTiedotFormValues } from "./KuulutuksenTiedot";

type Props = {};
const defaultYhteystieto: YhteystietoInput = {
etunimi: "",
sukunimi: "",
organisaatio: "",
puhelinnumero: "",
sahkoposti: "",
};

interface Props {}

export default function EsitettavatYhteystiedot({}: Props): ReactElement {
const { data: projekti } = useProjektiRoute();

const eiVoiMuokata = projekti?.nahtavillaoloVaihe?.tila === NahtavillaoloVaiheTila.HYVAKSYTTY;

const {
register,
formState: { errors },
control,
} = useFormContext<KuulutuksenTiedotFormValues>();

const { fields, append, remove } = useFieldArray({
control,
name: "nahtavillaoloVaihe.kuulutusYhteystiedot",
});

const vuorovaikutusYhteysHenkilot: ProjektiKayttaja[] = projekti?.nahtavillaoloVaihe?.kuulutusYhteysHenkilot
? projekti.nahtavillaoloVaihe.kuulutusYhteysHenkilot
.map((hlo) => {
const yhteysHenkiloTietoineen: ProjektiKayttaja | undefined = (projekti?.kayttoOikeudet || []).find(
(ko) => ko.kayttajatunnus === hlo
);
if (!yhteysHenkiloTietoineen) {
return {} as ProjektiKayttaja;
}
return yhteysHenkiloTietoineen as ProjektiKayttaja;
})
.filter((pk) => pk.nimi)
: ([] as ProjektiKayttaja[]);

if (eiVoiMuokata) {
return (
<Section>
<SectionContent>
<p className="vayla-label mb-5">Vuorovaikuttamisen yhteyshenkilöt</p>
{projekti?.nahtavillaoloVaihe?.kuulutusYhteystiedot?.map((yhteystieto, index) => (
<p style={{ margin: 0 }} key={index}>
{capitalize(yhteystieto.etunimi)} {capitalize(yhteystieto.sukunimi)}, puh. {yhteystieto.puhelinnumero},{" "}
{yhteystieto?.sahkoposti ? replace(yhteystieto?.sahkoposti, "@", "[at]") : ""} ({yhteystieto.organisaatio}
)
</p>
))}
{vuorovaikutusYhteysHenkilot.map((yhteystieto, index) => (
<p style={{ margin: 0 }} key={index}>
{yhteystieto.nimi}, puh. {yhteystieto.puhelinnumero},{" "}
{yhteystieto.email ? replace(yhteystieto.email, "@", "[at]") : ""} ({yhteystieto.organisaatio})
</p>
))}
</SectionContent>
</Section>
);
}

export default function KuulutuksessaEsitettavatYhteystiedot({}: Props) {
return (
<Section>
<h4 className="vayla-small-title">Kuulutuksessa esitettävät yhteystiedot</h4>
<p>
Voit valita kuulutuksessa esitettäviin yhteystietoihin projektiin tallennetun henkilön tai lisätä uuden
yhteystiedon. Projektipäällikön tiedot esitetään aina. Projektiin tallennettujen henkilöiden yhteystiedot
haetaan Projektin henkilöt -sivulle tallennetuista tiedoista.
</p>
<SectionContent>
<h4 className="vayla-small-title">Kuulutuksessa esitettävät yhteystiedot</h4>
<p>
Voit valita kutsussa esitettäviin yhteystietoihin projektiin tallennetun henkilön tai lisätä uuden
yhteystiedon. Projektipäällikön tiedot esitetään aina. Projektiin tallennettujen henkilöiden yhteystiedot
haetaan Projektin henkilöt -sivulle tallennetuista tiedoista.
</p>
{projekti?.kayttoOikeudet && projekti.kayttoOikeudet.length > 0 ? (
<Controller
control={control}
name={`nahtavillaoloVaihe.kuulutusYhteysHenkilot`}
render={({ field: { onChange, value, ...field } }) => (
<FormGroup label="Projektiin tallennetut henkilöt" inlineFlex>
{projekti?.suunnitteluSopimus && (
<CheckBox
label={`${projekti.suunnitteluSopimus.sukunimi}, ${projekti.suunnitteluSopimus.etunimi}`}
disabled
defaultChecked
/>
)}
{projekti.kayttoOikeudet?.map(({ nimi, rooli, kayttajatunnus }, index) => {
const tunnuslista: string[] = value || [];
return (
<Fragment key={index}>
{rooli === ProjektiRooli.PROJEKTIPAALLIKKO ? (
<CheckBox label={nimi} disabled checked {...field} />
) : (
<CheckBox
label={nimi}
onChange={(event) => {
if (!event.target.checked) {
onChange(tunnuslista.filter((tunnus) => tunnus !== kayttajatunnus));
} else {
onChange([...tunnuslista, kayttajatunnus]);
}
}}
checked={tunnuslista.includes(kayttajatunnus)}
{...field}
/>
)}
</Fragment>
);
})}
</FormGroup>
)}
/>
) : (
<p>Projektilla ei ole tallennettuja henkilöitä</p>
)}
</SectionContent>
<SectionContent>
<p>Uusi yhteystieto</p>
<p>
Lisää uudelle yhteystiedolle rivi Lisää uusi-painikkeella. Huomioi, että uusi yhteystieto ei tallennu
Projektin henkilöt -sivulle eikä henkilölle tule käyttöoikeuksia projektiin.
</p>
</SectionContent>
{fields.map((field, index) => (
<HassuStack key={field.id} direction={["column", "column", "row"]}>
<HassuGrid sx={{ width: "100%" }} cols={[1, 1, 3]}>
<TextInput
label="Etunimi *"
{...register(`nahtavillaoloVaihe.kuulutusYhteystiedot.${index}.etunimi`)}
error={(errors as any).nahtavillaoloVaihe?.kuulutusYhteystiedot?.[index]?.etunimi}
/>
<TextInput
label="Sukunimi *"
{...register(`nahtavillaoloVaihe.kuulutusYhteystiedot.${index}.sukunimi`)}
error={(errors as any).nahtavillaoloVaihe?.kuulutusYhteystiedot?.[index]?.sukunimi}
/>
<TextInput
label="Organisaatio / kunta *"
{...register(`nahtavillaoloVaihe.kuulutusYhteystiedot.${index}.organisaatio`)}
error={(errors as any).nahtavillaoloVaihe?.kuulutusYhteystiedot?.[index]?.organisaatio}
/>
<TextInput
label="Puhelinnumero *"
{...register(`nahtavillaoloVaihe.kuulutusYhteystiedot.${index}.puhelinnumero`)}
error={(errors as any).nahtavillaoloVaihe?.kuulutusYhteystiedot?.[index]?.puhelinnumero}
maxLength={maxPhoneLength}
/>
<TextInput
label="Sähköpostiosoite *"
{...register(`nahtavillaoloVaihe.kuulutusYhteystiedot.${index}.sahkoposti`)}
error={(errors as any).nahtavillaoloVaihe?.kuulutusYhteystiedot?.[index]?.sahkoposti}
/>
</HassuGrid>
<div>
<div className="hidden lg:block lg:mt-8">
<IconButton
icon="trash"
onClick={(event) => {
event.preventDefault();
remove(index);
}}
/>
</div>
<div className="block lg:hidden">
<Button
onClick={(event) => {
event.preventDefault();
remove(index);
}}
endIcon="trash"
>
Poista
</Button>
</div>
</div>
</HassuStack>
))}
<Button
onClick={(event) => {
event.preventDefault();
append(defaultYhteystieto);
}}
>
Lisää uusi +
</Button>
</Section>
);
}
1 change: 0 additions & 1 deletion src/hooks/useProjekti.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ async function projektiLoader(_: string, oid: string | undefined, kayttaja: Nyky
return null;
}
const projekti = await api.lataaProjekti(oid);
console.log("projekti", projekti);
const lisatiedot: ProjektiLisatiedot = {
nykyinenKayttaja: {
omaaMuokkausOikeuden: userIsAdmin(kayttaja) || userHasAccessToProjekti({ projekti, kayttaja }),
Expand Down
16 changes: 15 additions & 1 deletion src/schemas/nahtavillaoloKuulutus.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as Yup from "yup";
import { isDevEnvironment } from "@services/config";
import { yhteystietoSchema } from "./yhteystieto";

function validateDate(dateString: string) {
try {
Expand All @@ -13,11 +14,24 @@ function validateDate(dateString: string) {
}
}

const maxNahtavillaoloLength = 2000;

let hankkeenKuvaus = Yup.string()
.max(
maxNahtavillaoloLength,
`Nähtävilläolovaiheeseen voidaan kirjoittaa maksimissaan ${maxNahtavillaoloLength} merkkiä`
)
.required("Hankkeen kuvaus ei voi olla tyhjä")
.nullable();

export const nahtavillaoloKuulutusSchema = Yup.object().shape({
oid: Yup.string().required(),
nahtavillaoloVaihe: Yup.object()
.required()
.shape({
kuulutusYhteystiedot: Yup.array().notRequired().of(yhteystietoSchema),
kuulutusYhteysHenkilot: Yup.array().notRequired().of(Yup.string()),
hankkeenKuvaus: Yup.object().shape({ SUOMI: hankkeenKuvaus }),
kuulutusPaiva: Yup.string()
.required("Kuulutuspäivä ei voi olla tyhjä")
.nullable()
Expand All @@ -37,7 +51,7 @@ export const nahtavillaoloKuulutusSchema = Yup.object().shape({
return dateString >= todayISODate;
}),
kuulutusVaihePaattyyPaiva: Yup.string().test("is-valid-date", "Virheellinen päivämäärä", (dateString) => {
// siirtyyLainvoimaan is not required when saved as a draft.
// kuulutusVaihePaattyyPaiva is not required when saved as a draft.
// This test doesn't throw errors if date is not set.
if (!dateString) {
return true;
Expand Down
1 change: 1 addition & 0 deletions src/schemas/vuorovaikutus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export const vuorovaikutusSchema = Yup.object().shape({
.test("valid-date", "Virheellinen päivämäärä", (date) => {
return isValidDate(date);
}),
vuorovaikutusYhteysHenkilot: Yup.array().notRequired().of(Yup.string()),
esitettavatYhteystiedot: Yup.array().notRequired().of(yhteystietoSchema),
vuorovaikutusTilaisuudet: Yup.array()
.of(vuorovaikutustilaisuudetSchema)
Expand Down

0 comments on commit fca1999

Please sign in to comment.