CI/CD / preconditions (pull_request) Successful in 1s
macOS checks / Native Rust tests and unsigned build (pull_request) Canceled after 0s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m11s
CI/CD / test_and_check_app (pull_request) Successful in 5m58s
CI/CD / Publish immutable image (pull_request) Skipped
158 lines
5.7 KiB
Vue
158 lines
5.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import TicketDescriptionEditor from '@/components/tickets/TicketDescriptionEditor.vue';
|
|
import TicketFormField from '@/components/tickets/TicketFormField.vue';
|
|
import { useLocalization } from '@/composables/useLocalization';
|
|
import { ticketStatusLabel } from '@/lib/tickets';
|
|
import type { ProjectTicketCreatePageProps } from '@/types';
|
|
|
|
const props = defineProps<
|
|
Pick<
|
|
ProjectTicketCreatePageProps,
|
|
| 'project'
|
|
| 'ticketTypes'
|
|
| 'ticketPriorities'
|
|
| 'users'
|
|
| 'parentTicketOptions'
|
|
> & {
|
|
errors: Record<string, string>;
|
|
disabled?: boolean;
|
|
autofocus?: boolean;
|
|
ticketStatusOptions?: ProjectTicketCreatePageProps['ticketStatusOptions'];
|
|
}
|
|
>();
|
|
const title = defineModel<string>('title', { required: true });
|
|
const description = defineModel<string>('description', { required: true });
|
|
const type = defineModel<string | number>('type', { required: true });
|
|
const priority = defineModel<string | number>('priority', { required: true });
|
|
const assignee = defineModel<string | number>('assignee', { required: true });
|
|
const parent = defineModel<string | number>('parent', { required: true });
|
|
const points = defineModel<string | number | null>('points', {
|
|
required: true,
|
|
});
|
|
const status = defineModel<string | number>('status');
|
|
const { t } = useLocalization();
|
|
const typeOptions = computed(() =>
|
|
props.ticketTypes.map((item) => ({ value: item.id, label: item.name })),
|
|
);
|
|
const priorityOptions = computed(() =>
|
|
props.ticketPriorities.map((item) => ({
|
|
value: item.id,
|
|
label: item.name,
|
|
})),
|
|
);
|
|
const userOptions = computed(() => [
|
|
{ value: '', label: t('tickets.unassigned') },
|
|
...props.users.map((item) => ({ value: item.id, label: item.name })),
|
|
]);
|
|
const parentOptions = computed(() => [
|
|
{ value: '', label: t('ticketDetail.noParent') },
|
|
...props.parentTicketOptions.map((item) => ({
|
|
value: item.id,
|
|
label: `${item.ticket_key} - ${item.title}`,
|
|
})),
|
|
]);
|
|
const statusOptions = computed(
|
|
() =>
|
|
props.ticketStatusOptions?.map((item) => ({
|
|
value: item.id,
|
|
label: ticketStatusLabel(item, t('ticketDetail.notSet')),
|
|
})) ?? [],
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="grid min-w-0 gap-6">
|
|
<TicketFormField
|
|
id="title"
|
|
v-model="title"
|
|
:label="t('ticketDetail.title')"
|
|
:error="errors.title"
|
|
:disabled="disabled"
|
|
:autofocus="autofocus"
|
|
:placeholder="t('ticketForm.titlePlaceholder')"
|
|
required
|
|
/>
|
|
<TicketDescriptionEditor
|
|
id="description"
|
|
v-model="description"
|
|
:label="t('ticketDetail.description')"
|
|
:error="errors.description"
|
|
:disabled="disabled"
|
|
:placeholder="t('ticketForm.descriptionPlaceholder')"
|
|
/>
|
|
<slot name="content" />
|
|
<fieldset
|
|
class="bg-muted/20 grid min-w-0 gap-5 rounded-xl border p-4 sm:p-5"
|
|
:disabled="disabled"
|
|
>
|
|
<legend class="px-2 text-sm font-semibold">
|
|
{{ t('ticketDetail.metadata') }}
|
|
</legend>
|
|
<div class="grid min-w-0 gap-5 sm:grid-cols-2">
|
|
<TicketFormField
|
|
id="ticket_type_id"
|
|
v-model="type"
|
|
kind="select"
|
|
:label="t('ticketDetail.type')"
|
|
:options="typeOptions"
|
|
:error="errors.ticket_type_id"
|
|
:disabled="disabled"
|
|
required
|
|
/>
|
|
<TicketFormField
|
|
id="ticket_priority_id"
|
|
v-model="priority"
|
|
kind="select"
|
|
:label="t('ticketDetail.priority')"
|
|
:options="priorityOptions"
|
|
:error="errors.ticket_priority_id"
|
|
:disabled="disabled"
|
|
required
|
|
/>
|
|
<TicketFormField
|
|
v-if="ticketStatusOptions && status !== undefined"
|
|
id="ticket_status_id"
|
|
v-model="status"
|
|
kind="select"
|
|
:label="t('ticketDetail.status')"
|
|
:options="statusOptions"
|
|
:error="errors.ticket_status_id"
|
|
:disabled="disabled"
|
|
required
|
|
/>
|
|
<TicketFormField
|
|
id="assignee_user_id"
|
|
v-model="assignee"
|
|
kind="select"
|
|
:label="t('ticketDetail.assignee')"
|
|
:options="userOptions"
|
|
:error="errors.assignee_user_id"
|
|
:disabled="disabled"
|
|
/>
|
|
<TicketFormField
|
|
id="parent_ticket_id"
|
|
v-model="parent"
|
|
kind="select"
|
|
:label="t('ticketDetail.parent')"
|
|
:options="parentOptions"
|
|
:error="errors.parent_ticket_id"
|
|
:disabled="disabled"
|
|
/>
|
|
<TicketFormField
|
|
v-if="project.show_story_points"
|
|
id="story_points"
|
|
v-model="points"
|
|
kind="number"
|
|
:label="t('ticketDetail.storyPoints')"
|
|
:error="errors.story_points"
|
|
:disabled="disabled"
|
|
:min="0"
|
|
step="1"
|
|
/>
|
|
<slot name="metadata" />
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
</template>
|