feat(organisation): rebuild EditOrganisationDialog with contact fields
Vervang het naam-alleen dialoog door een volledig organisatiegegevens- formulier: naam, slug (met copy-knop en tooltip), contactpersoon, contact e-mail, telefoon en website. Slug krijgt een regex-validator; e-mail en URL alleen gevalideerd wanneer ingevuld. Server-side validatiefouten per veld getoond. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { VForm } from 'vuetify/components/VForm'
|
||||
import { useUpdateOrganisation } from '@/composables/api/useOrganisations'
|
||||
import { requiredValidator } from '@core/utils/validators'
|
||||
import { emailValidator, requiredValidator, urlValidator } from '@core/utils/validators'
|
||||
import type { Organisation } from '@/types/organisation'
|
||||
import type { AxiosError } from 'axios'
|
||||
import type { ApiErrorResponse } from '@/types/auth'
|
||||
@@ -12,16 +12,52 @@ const props = defineProps<{
|
||||
|
||||
const modelValue = defineModel<boolean>({ required: true })
|
||||
|
||||
const name = ref('')
|
||||
interface FormState {
|
||||
name: string
|
||||
slug: string
|
||||
contact_name: string
|
||||
contact_email: string
|
||||
phone: string
|
||||
website: string
|
||||
}
|
||||
|
||||
const initialState = (): FormState => ({
|
||||
name: props.organisation.name ?? '',
|
||||
slug: props.organisation.slug ?? '',
|
||||
contact_name: props.organisation.contact_name ?? '',
|
||||
contact_email: props.organisation.contact_email ?? '',
|
||||
phone: props.organisation.phone ?? '',
|
||||
website: props.organisation.website ?? '',
|
||||
})
|
||||
|
||||
const form = ref<FormState>(initialState())
|
||||
const errors = ref<Record<string, string>>({})
|
||||
const refVForm = ref<VForm>()
|
||||
const showSuccess = ref(false)
|
||||
const showCopied = ref(false)
|
||||
|
||||
const { mutate: updateOrganisation, isPending } = useUpdateOrganisation()
|
||||
|
||||
watch(() => props.organisation, (org) => {
|
||||
name.value = org.name
|
||||
}, { immediate: true })
|
||||
const slugRegexValidator = (value: string) =>
|
||||
/^[a-z0-9-]+$/.test(value) || 'Alleen kleine letters, cijfers en streepjes toegestaan.'
|
||||
|
||||
watch(
|
||||
[() => props.organisation, modelValue],
|
||||
([, isOpen]) => {
|
||||
if (isOpen) {
|
||||
form.value = initialState()
|
||||
errors.value = {}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function copySlug() {
|
||||
if (form.value.slug) {
|
||||
navigator.clipboard.writeText(form.value.slug)
|
||||
showCopied.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
refVForm.value?.validate().then(({ valid }) => {
|
||||
@@ -29,24 +65,32 @@ function onSubmit() {
|
||||
|
||||
errors.value = {}
|
||||
|
||||
updateOrganisation(
|
||||
{ id: props.organisation.id, name: name.value },
|
||||
{
|
||||
onSuccess: () => {
|
||||
modelValue.value = false
|
||||
showSuccess.value = true
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
const data = (err as AxiosError<ApiErrorResponse>).response?.data
|
||||
if (data?.errors) {
|
||||
errors.value = { name: data.errors.name?.[0] ?? '' }
|
||||
}
|
||||
else if (data?.message) {
|
||||
errors.value = { name: data.message }
|
||||
}
|
||||
},
|
||||
const payload = {
|
||||
id: props.organisation.id,
|
||||
name: form.value.name,
|
||||
slug: form.value.slug,
|
||||
contact_name: form.value.contact_name || null,
|
||||
contact_email: form.value.contact_email || null,
|
||||
phone: form.value.phone || null,
|
||||
website: form.value.website || null,
|
||||
}
|
||||
|
||||
updateOrganisation(payload, {
|
||||
onSuccess: () => {
|
||||
modelValue.value = false
|
||||
showSuccess.value = true
|
||||
},
|
||||
)
|
||||
onError: (err: Error) => {
|
||||
const data = (err as AxiosError<ApiErrorResponse>).response?.data
|
||||
if (data?.errors) {
|
||||
const fieldErrors: Record<string, string> = {}
|
||||
for (const [key, messages] of Object.entries(data.errors)) {
|
||||
fieldErrors[key] = messages[0] ?? ''
|
||||
}
|
||||
errors.value = fieldErrors
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -54,27 +98,108 @@ function onSubmit() {
|
||||
<template>
|
||||
<VDialog
|
||||
v-model="modelValue"
|
||||
max-width="450"
|
||||
max-width="640"
|
||||
>
|
||||
<VCard title="Naam bewerken">
|
||||
<VCard title="Organisatiegegevens bewerken">
|
||||
<VForm
|
||||
ref="refVForm"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<VCardText>
|
||||
<AppTextField
|
||||
v-model="name"
|
||||
label="Organisatienaam"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.name"
|
||||
autofocus
|
||||
autocomplete="one-time-code"
|
||||
/>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.name"
|
||||
label="Organisatienaam"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.name"
|
||||
autofocus
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.slug"
|
||||
label="Slug"
|
||||
:rules="[requiredValidator, slugRegexValidator]"
|
||||
:error-messages="errors.slug"
|
||||
>
|
||||
<template #append-inner>
|
||||
<VTooltip text="URL-veilige identificatie — wijzigen kan bestaande links breken">
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<VIcon
|
||||
v-bind="tooltipProps"
|
||||
icon="tabler-info-circle"
|
||||
size="18"
|
||||
color="disabled"
|
||||
/>
|
||||
</template>
|
||||
</VTooltip>
|
||||
<VBtn
|
||||
icon="tabler-copy"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
@click="copySlug"
|
||||
/>
|
||||
</template>
|
||||
</AppTextField>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.contact_name"
|
||||
label="Contactpersoon"
|
||||
:error-messages="errors.contact_name"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.contact_email"
|
||||
label="Contact e-mail"
|
||||
type="email"
|
||||
:rules="form.contact_email ? [emailValidator] : []"
|
||||
:error-messages="errors.contact_email"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.phone"
|
||||
label="Telefoon"
|
||||
:error-messages="errors.phone"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.website"
|
||||
label="Website"
|
||||
placeholder="https://example.com"
|
||||
:rules="form.website ? [urlValidator] : []"
|
||||
:error-messages="errors.website"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
:disabled="isPending"
|
||||
@click="modelValue = false"
|
||||
>
|
||||
Annuleren
|
||||
@@ -84,7 +209,7 @@ function onSubmit() {
|
||||
color="primary"
|
||||
:loading="isPending"
|
||||
>
|
||||
Opslaan
|
||||
Wijzigingen opslaan
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VForm>
|
||||
@@ -96,6 +221,13 @@ function onSubmit() {
|
||||
color="success"
|
||||
:timeout="3000"
|
||||
>
|
||||
Naam bijgewerkt
|
||||
Organisatie bijgewerkt
|
||||
</VSnackbar>
|
||||
<VSnackbar
|
||||
v-model="showCopied"
|
||||
color="info"
|
||||
:timeout="2000"
|
||||
>
|
||||
Slug gekopieerd
|
||||
</VSnackbar>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user