Files
Lesstime/frontend/pages/portal/projects/[id]/new-ticket.vue
2026-03-15 20:04:20 +01:00

135 lines
4.7 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">
<MalioInputTextArea
v-model="form.description"
:label="$t('clientTicket.description')"
:size="5"
/>
</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>
<button
type="submit"
class="rounded-lg bg-primary-500 px-6 py-2 text-sm font-semibold text-white transition-colors hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
:disabled="isSubmitting"
>
{{ $t('portal.submitTicket') }}
</button>
</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>