diff --git a/src/components/SearchComponent.vue b/src/components/SearchComponent.vue
index 037cc677..8cf2e554 100644
--- a/src/components/SearchComponent.vue
+++ b/src/components/SearchComponent.vue
@@ -95,19 +95,21 @@ const clear = () => {
-
-
-
-
-
+
+
+
+
+
+
+
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'
@@ -11,6 +12,9 @@ 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: {
@@ -49,22 +53,35 @@ const bouquetOptions = computed(() => {
})
})
+const formErrors: Ref = 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 = computed(() => {
return [
{
label: 'Annuler',
@@ -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
@@ -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)
}
@@ -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"
>
+
-
+ :aria-invalid="
+ formErrors.includes('bouquetId') && isSubmitted ? true : undefined
+ "
+ aria-errormessage="errors-bouquetId"
+ />
+
+
{
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')"
/>
+
{
.fr-select-group:has(+ .fr-badge) {
margin-bottom: 0.5rem;
}
+:deep(.fr-select-group:has(+ #errors-bouquetId)) {
+ margin-bottom: 0;
+}
diff --git a/src/components/forms/ErrorMessage.vue b/src/components/forms/ErrorMessage.vue
new file mode 100644
index 00000000..7398ce3b
--- /dev/null
+++ b/src/components/forms/ErrorMessage.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+ {{ errorMessage }}
+
+
diff --git a/src/components/forms/ErrorSummary.vue b/src/components/forms/ErrorSummary.vue
new file mode 100644
index 00000000..5704615f
--- /dev/null
+++ b/src/components/forms/ErrorSummary.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
diff --git a/src/components/forms/SelectTopicGroup.vue b/src/components/forms/SelectTopicGroup.vue
index 9232dc9e..dc133752 100644
--- a/src/components/forms/SelectTopicGroup.vue
+++ b/src/components/forms/SelectTopicGroup.vue
@@ -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,
@@ -26,6 +27,10 @@ defineProps({
required: {
type: Boolean,
default: false
+ },
+ errorMessage: {
+ type: String,
+ default: ''
}
})
@@ -44,7 +49,7 @@ const trimGroupName = (groupName: string) => {
-
{
placeholder=""
:aria="{
'aria-describedby': 'regroupement-description',
+ 'aria-errormessage': 'errors-group',
// 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"
>
@@ -96,4 +103,9 @@ const trimGroupName = (groupName: string) => {
+
diff --git a/src/components/forms/bouquet/BouquetForm.vue b/src/components/forms/bouquet/BouquetForm.vue
index 1029cbd2..7cb4587a 100644
--- a/src/components/forms/bouquet/BouquetForm.vue
+++ b/src/components/forms/bouquet/BouquetForm.vue
@@ -119,6 +119,7 @@ onMounted(() => {