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">
|
<script setup lang="ts">
|
||||||
import { VForm } from 'vuetify/components/VForm'
|
import { VForm } from 'vuetify/components/VForm'
|
||||||
import { useUpdateOrganisation } from '@/composables/api/useOrganisations'
|
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 { Organisation } from '@/types/organisation'
|
||||||
import type { AxiosError } from 'axios'
|
import type { AxiosError } from 'axios'
|
||||||
import type { ApiErrorResponse } from '@/types/auth'
|
import type { ApiErrorResponse } from '@/types/auth'
|
||||||
@@ -12,16 +12,52 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const modelValue = defineModel<boolean>({ required: true })
|
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 errors = ref<Record<string, string>>({})
|
||||||
const refVForm = ref<VForm>()
|
const refVForm = ref<VForm>()
|
||||||
const showSuccess = ref(false)
|
const showSuccess = ref(false)
|
||||||
|
const showCopied = ref(false)
|
||||||
|
|
||||||
const { mutate: updateOrganisation, isPending } = useUpdateOrganisation()
|
const { mutate: updateOrganisation, isPending } = useUpdateOrganisation()
|
||||||
|
|
||||||
watch(() => props.organisation, (org) => {
|
const slugRegexValidator = (value: string) =>
|
||||||
name.value = org.name
|
/^[a-z0-9-]+$/.test(value) || 'Alleen kleine letters, cijfers en streepjes toegestaan.'
|
||||||
}, { immediate: true })
|
|
||||||
|
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() {
|
function onSubmit() {
|
||||||
refVForm.value?.validate().then(({ valid }) => {
|
refVForm.value?.validate().then(({ valid }) => {
|
||||||
@@ -29,24 +65,32 @@ function onSubmit() {
|
|||||||
|
|
||||||
errors.value = {}
|
errors.value = {}
|
||||||
|
|
||||||
updateOrganisation(
|
const payload = {
|
||||||
{ id: props.organisation.id, name: name.value },
|
id: props.organisation.id,
|
||||||
{
|
name: form.value.name,
|
||||||
onSuccess: () => {
|
slug: form.value.slug,
|
||||||
modelValue.value = false
|
contact_name: form.value.contact_name || null,
|
||||||
showSuccess.value = true
|
contact_email: form.value.contact_email || null,
|
||||||
},
|
phone: form.value.phone || null,
|
||||||
onError: (err: Error) => {
|
website: form.value.website || null,
|
||||||
const data = (err as AxiosError<ApiErrorResponse>).response?.data
|
}
|
||||||
if (data?.errors) {
|
|
||||||
errors.value = { name: data.errors.name?.[0] ?? '' }
|
updateOrganisation(payload, {
|
||||||
}
|
onSuccess: () => {
|
||||||
else if (data?.message) {
|
modelValue.value = false
|
||||||
errors.value = { name: data.message }
|
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>
|
</script>
|
||||||
@@ -54,27 +98,108 @@ function onSubmit() {
|
|||||||
<template>
|
<template>
|
||||||
<VDialog
|
<VDialog
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
max-width="450"
|
max-width="640"
|
||||||
>
|
>
|
||||||
<VCard title="Naam bewerken">
|
<VCard title="Organisatiegegevens bewerken">
|
||||||
<VForm
|
<VForm
|
||||||
ref="refVForm"
|
ref="refVForm"
|
||||||
@submit.prevent="onSubmit"
|
@submit.prevent="onSubmit"
|
||||||
>
|
>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<AppTextField
|
<VRow>
|
||||||
v-model="name"
|
<VCol
|
||||||
label="Organisatienaam"
|
cols="12"
|
||||||
:rules="[requiredValidator]"
|
md="6"
|
||||||
:error-messages="errors.name"
|
>
|
||||||
autofocus
|
<AppTextField
|
||||||
autocomplete="one-time-code"
|
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>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<VSpacer />
|
<VSpacer />
|
||||||
<VBtn
|
<VBtn
|
||||||
variant="text"
|
variant="text"
|
||||||
|
:disabled="isPending"
|
||||||
@click="modelValue = false"
|
@click="modelValue = false"
|
||||||
>
|
>
|
||||||
Annuleren
|
Annuleren
|
||||||
@@ -84,7 +209,7 @@ function onSubmit() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
:loading="isPending"
|
:loading="isPending"
|
||||||
>
|
>
|
||||||
Opslaan
|
Wijzigingen opslaan
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</VCardActions>
|
</VCardActions>
|
||||||
</VForm>
|
</VForm>
|
||||||
@@ -96,6 +221,13 @@ function onSubmit() {
|
|||||||
color="success"
|
color="success"
|
||||||
:timeout="3000"
|
:timeout="3000"
|
||||||
>
|
>
|
||||||
Naam bijgewerkt
|
Organisatie bijgewerkt
|
||||||
|
</VSnackbar>
|
||||||
|
<VSnackbar
|
||||||
|
v-model="showCopied"
|
||||||
|
color="info"
|
||||||
|
:timeout="2000"
|
||||||
|
>
|
||||||
|
Slug gekopieerd
|
||||||
</VSnackbar>
|
</VSnackbar>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user