Remplace les éditeurs markdown locaux et les textareas description par <MalioInputRichText> (TipTap v3 + StarterKit + tiptap-markdown) du paquet @malio/layer-ui. Sites migrés : - TaskModal (description tâche) - TaskGroupDrawer (description groupe de tâches) - TimeEntryDrawer (description time entry) - ClientTicketDetailModal (édition + lecture seule) - ProjectClientTickets (panneau admin lecture seule) - new-ticket (formulaire portail client) - client-tickets (vue admin lecture seule) Stockage en BDD inchangé : le markdown existant est parsé à l'ouverture, le composant émet du HTML par défaut sur les sauvegardes (migration lazy au fil des éditions). Bumpe @malio/layer-ui de ^1.2.3 à ^1.4.7 et ajoute les dépendances TipTap utilisées par le composant. Co-Authored-By: RuFlo <ruv@ruv.net>
134 lines
4.6 KiB
Vue
134 lines
4.6 KiB
Vue
<template>
|
|
<div>
|
|
<div class="sticky top-8 z-20 bg-white pb-4 sm:top-12">
|
|
<NuxtLink
|
|
:to="`/portal/projects/${projectId}`"
|
|
class="text-sm text-neutral-400 hover:text-primary-500"
|
|
>
|
|
{{ $t('portal.backToProject') }}
|
|
</NuxtLink>
|
|
<h1 class="mt-1 text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('portal.newTicket') }}</h1>
|
|
</div>
|
|
|
|
<form class="mt-4 max-w-2xl" @submit.prevent="handleSubmit">
|
|
<!-- Type -->
|
|
<div>
|
|
<label class="mb-1 block text-sm font-medium text-neutral-700">{{ $t('clientTicket.selectType') }}</label>
|
|
<select
|
|
v-model="form.type"
|
|
class="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
>
|
|
<option value="bug">{{ $t('clientTicket.type.bug') }}</option>
|
|
<option value="improvement">{{ $t('clientTicket.type.improvement') }}</option>
|
|
<option value="other">{{ $t('clientTicket.type.other') }}</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Title -->
|
|
<div class="mt-4">
|
|
<MalioInputText
|
|
v-model="form.title"
|
|
:label="$t('clientTicket.title')"
|
|
input-class="w-full"
|
|
:error="touched.title && !form.title.trim() ? $t('clientTicket.title') + ' requis' : ''"
|
|
@blur="touched.title = true"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<div class="mt-4">
|
|
<MalioInputRichText
|
|
v-model="form.description"
|
|
:label="$t('clientTicket.description')"
|
|
min-height="180px"
|
|
/>
|
|
</div>
|
|
|
|
<!-- URL (only for bug type) -->
|
|
<div v-if="form.type === 'bug'" class="mt-4">
|
|
<MalioInputText
|
|
v-model="form.url"
|
|
:label="$t('clientTicket.url')"
|
|
input-class="w-full"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Document upload (only after ticket is created) -->
|
|
<div class="mt-4 rounded-lg border border-dashed border-neutral-300 p-4">
|
|
<p class="text-sm text-neutral-500">
|
|
<Icon name="heroicons:information-circle" class="mr-1 inline h-4 w-4" />
|
|
Les documents pourront être ajoutés après la soumission du ticket.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Submit -->
|
|
<div class="mt-6 flex items-center gap-3">
|
|
<NuxtLink
|
|
:to="`/portal/projects/${projectId}`"
|
|
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50"
|
|
>
|
|
{{ $t('common.cancel') }}
|
|
</NuxtLink>
|
|
<MalioButton
|
|
:label="$t('portal.submitTicket')"
|
|
button-class="w-auto px-6"
|
|
:disabled="isSubmitting"
|
|
@click="handleSubmit"
|
|
/>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { ClientTicketType } from '~/services/dto/client-ticket'
|
|
import { useClientTicketService } from '~/services/client-tickets'
|
|
|
|
definePageMeta({
|
|
layout: 'portal',
|
|
})
|
|
|
|
const route = useRoute()
|
|
const { t } = useI18n()
|
|
const projectId = computed(() => Number(route.params.id))
|
|
|
|
useHead({ title: t('portal.newTicket') })
|
|
|
|
const clientTicketService = useClientTicketService()
|
|
|
|
const form = reactive({
|
|
type: 'bug' as ClientTicketType | string,
|
|
title: '',
|
|
description: '',
|
|
url: '',
|
|
})
|
|
|
|
const touched = reactive({
|
|
title: false,
|
|
})
|
|
|
|
const isSubmitting = ref(false)
|
|
|
|
async function handleSubmit() {
|
|
touched.title = true
|
|
if (!form.title.trim()) return
|
|
if (!form.description.trim()) return
|
|
|
|
isSubmitting.value = true
|
|
try {
|
|
await clientTicketService.create({
|
|
type: form.type as ClientTicketType,
|
|
title: form.title.trim(),
|
|
description: form.description.trim(),
|
|
url: form.type === 'bug' && form.url.trim() ? form.url.trim() : null,
|
|
project: `/api/projects/${projectId.value}`,
|
|
})
|
|
await navigateTo(`/portal/projects/${projectId.value}`)
|
|
} catch {
|
|
// Toast already shown by useApi
|
|
} finally {
|
|
isSubmitting.value = false
|
|
}
|
|
}
|
|
</script>
|