Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: forms #656

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
28 changes: 15 additions & 13 deletions src/components/SearchComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,21 @@ const clear = () => {
</script>

<template>
<DsfrInputGroup
v-if="isFilter"
:id="id"
v-model="selectedQuery"
:label="searchLabel"
class="filter-input"
:label-visible="labelVisible"
:placeholder="!labelVisible ? searchLabel : undefined"
>
<template #before-input>
<VIconCustom name="search-line" class="search-icon" />
</template>
</DsfrInputGroup>
<search v-if="isFilter">
<DsfrInputGroup
:id="id"
v-model="selectedQuery"
:label="searchLabel"
class="filter-input"
type="search"
:label-visible="labelVisible"
:placeholder="!labelVisible ? searchLabel : undefined"
>
<template #before-input>
<VIconCustom name="search-line" class="search-icon" />
</template>
</DsfrInputGroup>
</search>

<DsfrSearchBar
v-else-if="!dropdown.length"
Expand Down
84 changes: 70 additions & 14 deletions src/components/datasets/DatasetAddToBouquetModal.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
<script setup lang="ts">
import type { DatasetV2 } from '@datagouv/components'
import { capitalize, computed, onMounted, ref } from 'vue'
import { capitalize, computed, onMounted, ref, type Ref } from 'vue'
import { useLoading } from 'vue-loading-overlay'
import { toast } from 'vue3-toastify'

import ErrorMessage from '@/components/forms/ErrorMessage.vue'
import DatasetPropertiesTextFields from '@/components/forms/dataset/DatasetPropertiesTextFields.vue'
import { Availability, type DatasetProperties } from '@/model/topic'
import { useTopicStore } from '@/store/TopicStore'
import { useTopicsConf } from '@/utils/config'

import { useExtras } from '@/utils/bouquet'
import { useGroups } from '@/utils/bouquetGroups'
import type { DsfrButtonGroupProps } from '@gouvminint/vue-dsfr'

import { useForm } from '@/utils/form'

const props = defineProps({
show: {
Expand Down Expand Up @@ -49,22 +53,35 @@ const bouquetOptions = computed(() => {
})
})

const formErrors: Ref<string[]> = ref([])

const validateFields = () => {
if (!datasetProperties.value.title.trim()) {
formErrors.value.push('title')
}
if (!datasetProperties.value.purpose.trim()) {
formErrors.value.push('purpose')
}
if (!selectedBouquetId.value) {
formErrors.value.push('bouquetId')
}
if (
datasetProperties.value.group &&
datasetProperties.value.group.trim().length > 100
) {
formErrors.value.push('group')
}
}

const isValid = computed(() => {
if (topicsDatasetEditorialization) {
return (
!!datasetProperties.value.title.trim() &&
!!datasetProperties.value.purpose.trim() &&
!!selectedBouquetId.value &&
(datasetProperties.value.group
? datasetProperties.value.group.trim().length < 100
: true)
)
return !formErrors.value.length
} else {
return !!selectedBouquetId.value
}
})

const modalActions = computed(() => {
const modalActions: Ref<DsfrButtonGroupProps['buttons']> = computed(() => {
return [
{
label: 'Annuler',
Expand All @@ -73,12 +90,13 @@ const modalActions = computed(() => {
},
{
label: 'Enregistrer',
disabled: !isValid.value,
onClick: () => submit()
onClick: () => handleSubmit()
}
]
})

const errorSummary = useTemplateRef('errorSummary')

const selectedBouquet = computed(() => {
if (selectedBouquetId.value === null) {
return null
Expand Down Expand Up @@ -125,6 +143,19 @@ const submit = async () => {
closeModal()
}

const {
formErrorMessagesMap,
sortedErrors,
getErrorMessage,
isSubmitted,
handleSubmit
} = useForm(formErrors, {
validateFields,
onSuccess: submit,
errorSummaryRef: errorSummary,
isValid
})

const closeModal = () => {
emit('update:show', false)
}
Expand All @@ -142,15 +173,33 @@ onMounted(() => {
:title="`Ajouter le jeu de données à un de vos ${topicsName}s`"
:opened="show"
aria-modal="true"
class="form"
@close="closeModal"
>
<ErrorSummary
v-show="formErrors.length"
ref="errorSummary"
:form-error-messages-map
:form-errors="sortedErrors"
heading-level="h3"
/>
<DsfrSelect
id="input-bouquetId"
v-model="selectedBouquetId"
:label="`${capitalize(topicsName)} à associer (obligatoire)`"
:options="bouquetOptions"
:default-unselected-text="`Choisissez un ${topicsName}`"
>
</DsfrSelect>
:aria-invalid="
formErrors.includes('bouquetId') && isSubmitted ? true : undefined
"
aria-describedby="errors-bouquetId"
/>
<ErrorMessage
v-if="!!getErrorMessage('bouquetId')"
input-name="bouquetId"
:error-message="getErrorMessage('bouquetId')"
/>

<DsfrBadge
v-if="isDatasetInBouquet"
type="info"
Expand All @@ -165,12 +214,16 @@ onMounted(() => {
v-model:groups-model="datasetsGroups"
label="Regroupement"
description="Rechercher ou créer un regroupement (100 caractères maximum). Un regroupement contient un ou plusieurs jeux de données."
:error-message="getErrorMessage('group')"
/>
</div>
<DatasetPropertiesTextFields
v-if="topicsDatasetEditorialization"
v-model:dataset-properties-model="datasetProperties"
:error-title="getErrorMessage('title')"
:error-purpose="getErrorMessage('purpose')"
/>

<slot name="footer">
<DsfrButtonGroup
v-if="modalActions?.length"
Expand All @@ -186,4 +239,7 @@ onMounted(() => {
.fr-select-group:has(+ .fr-badge) {
margin-bottom: 0.5rem;
}
:deep(.fr-select-group:has(+ #errors-bouquetId)) {
margin-bottom: 0;
}
</style>
13 changes: 13 additions & 0 deletions src/components/forms/ErrorMessage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script lang="ts" setup>
defineProps({
inputName: { type: String, required: true },
errorMessage: { type: String, required: true }
})
</script>

<template v-if="errorMessage">
<p :id="`errors-${inputName}`" class="error">
<VIconCustom name="error-fill" />
{{ errorMessage }}
</p>
</template>
41 changes: 41 additions & 0 deletions src/components/forms/ErrorSummary.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script lang="ts" setup>
import type { FormErrorMessagesMap } from '@/utils/form'

defineProps({
formErrors: {
type: Array<string>,
required: true
},
formErrorMessagesMap: {
type: Object as () => FormErrorMessagesMap,
required: true
},
headingLevel: {
type: String as () => 'h2' | 'h3' | 'h4' | 'h5',
required: true
}
})
</script>

<template>
<div
v-show="formErrors.length"
class="fr-my-4w fr-p-2w error-status"
role="group"
aria-labelledby="error-summary-title"
>
<component :is="headingLevel" id="error-summary-title" tabindex="-1">
Il y a {{ formErrors.length }} erreur<span v-if="formErrors.length > 1"
>s</span
>
de saisie dans le formulaire.
</component>
<ol>
<li v-for="(error, index) in formErrors" :key="index" class="error">
<a :href="`#input-${error}`">{{ formErrorMessagesMap.get(error) }}</a>
</li>
</ol>
</div>
</template>

<style scoped></style>
19 changes: 15 additions & 4 deletions src/components/forms/SelectTopicGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { DatasetProperties, DatasetsGroups } from '@/model/topic'

import Multiselect from '@vueform/multiselect'
import '@vueform/multiselect/themes/default.css'
import ErrorMessage from './ErrorMessage.vue'

const datasetProperties = defineModel('properties-model', {
type: Object as () => DatasetProperties,
Expand All @@ -26,6 +27,10 @@ defineProps({
required: {
type: Boolean,
default: false
},
errorMessage: {
type: String,
default: ''
}
})

Expand All @@ -44,7 +49,7 @@ const trimGroupName = (groupName: string) => {
</script>

<template>
<label for="regroupement-input">
<label for="input-group">
{{ label }}
(<span v-if="required">obligatoire</span><span v-else>facultatif</span>)
</label>
Expand All @@ -56,7 +61,7 @@ const trimGroupName = (groupName: string) => {
{{ description }}
</p>
<Multiselect
id="regroupement-input"
id="input-group"
v-model="datasetProperties.group"
role="search"
:options="groupOptions"
Expand All @@ -68,11 +73,12 @@ const trimGroupName = (groupName: string) => {
:create-option="true"
placeholder=""
:aria="{
'aria-describedby': 'regroupement-description',
'aria-describedby': 'errors-group regroupement-description',
// useless or unsupported https://github.com/vueform/multiselect/issues/436
'aria-labelledby': null,
'aria-multiselectable': null,
'aria-placeholder': null
'aria-placeholder': null,
'aria-invalid': `${!!errorMessage}`
}"
@select="trimGroupName"
>
Expand All @@ -96,4 +102,9 @@ const trimGroupName = (groupName: string) => {
</button>
</template>
</Multiselect>
<ErrorMessage
v-if="errorMessage"
input-name="group"
:error-message="errorMessage"
/>
</template>
2 changes: 2 additions & 0 deletions src/components/forms/bouquet/BouquetForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ onMounted(() => {
<!-- Title -->
<div class="fr-input-group">
<DsfrInput
id="input-name"
v-model="name"
v-bind="nameAttrs"
:label="`Sujet du ${topicsName} (obligatoire)`"
Expand All @@ -134,6 +135,7 @@ onMounted(() => {
<!-- Description -->
<div class="fr-input-group">
<DsfrInput
id="input-description"
v-model="description"
v-bind="descriptionAttrs"
is-textarea
Expand Down
Loading