Files
Lesstime/frontend/pages/portal/projects/[id]/new-ticket.vue
Matthieu 22373a0b87 refactor : migrate UI to Malio layer-ui components (MalioButton, MalioDrawer, MalioSelectCheckbox)
- Replace all AppDrawer with MalioDrawer across 10 drawer components
- Replace native <button> with MalioButton/MalioButtonIcon in all pages and components
- Fix TimeTrackingExportDrawer: use MalioSelectCheckbox for multi-select filters
- Add Malio design system colors (m-btn-*, m-disabled, m-surface) to tailwind.config.ts
- Align toggle button heights with MalioButton (h-[40px])

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 09:33:28 +01:00

139 lines
4.8 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"
resize="vertical"
:min-resize-height="140"
:max-resize-height="500"
min-resize-width="100%"
max-resize-width="100%"
/>
</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>