Implement ticket statuses

This commit is contained in:
2026-04-14 03:09:12 +02:00
parent 2dc12c5d60
commit 8f4a8276ac
40 changed files with 1602 additions and 100 deletions
+88 -13
View File
@@ -22,6 +22,8 @@ import { index, show } from '@/routes/projects/index';
import { show as showTicket } from '@/routes/projects/tickets';
import { metadata as metadataSettings } from '@/routes/projects/settings';
import type { BreadcrumbItem, ProjectBacklogPageProps } from '@/types';
import type { TicketModel } from '@/types/models/ticket-model';
import type { TicketStatusModel } from '@/types/models/ticket-status-model';
const props = defineProps<ProjectBacklogPageProps>();
@@ -41,7 +43,35 @@ const breadcrumbs: BreadcrumbItem[] = [
];
const hasTicketMetadata =
props.ticketTypes.length > 0 && props.ticketPriorities.length > 0;
props.ticketTypes.length > 0 &&
props.ticketPriorities.length > 0 &&
props.backlogTicketStatuses.length > 0;
const ticketStatusLabel = (ticketStatus: TicketStatusModel | null): string => {
if (ticketStatus === null) {
return 'Unknown status';
}
if (
ticketStatus.board_column === null ||
ticketStatus.board_column === undefined
) {
return ticketStatus.name;
}
if (ticketStatus.board_column.name === ticketStatus.name) {
return ticketStatus.name;
}
return `${ticketStatus.board_column.name} / ${ticketStatus.name}`;
};
const allowedTargetStatuses = (ticket: TicketModel): TicketStatusModel[] => {
return (
ticket.ticket_status?.allowed_target_statuses ??
props.ticketStatusOptions
);
};
</script>
<template>
@@ -85,7 +115,7 @@ const hasTicketMetadata =
<CardTitle>Create ticket</CardTitle>
<CardDescription>
New tickets receive the next immutable project key and
automatically enter backlog.
enter the selected backlog status.
</CardDescription>
</CardHeader>
@@ -182,6 +212,28 @@ const hasTicketMetadata =
/>
</div>
<div class="grid gap-2">
<Label for="ticket_status_id">
Status
</Label>
<select
id="ticket_status_id"
name="ticket_status_id"
class="border-input bg-background shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px]"
>
<option
v-for="ticketStatus in backlogTicketStatuses"
:key="ticketStatus.id"
:value="ticketStatus.id"
>
{{ ticketStatus.name }}
</option>
</select>
<InputError
:message="errors.ticket_status_id"
/>
</div>
<div class="grid gap-2">
<Label for="assignee_user_id">
Assignee
@@ -237,7 +289,7 @@ const hasTicketMetadata =
<CardContent class="flex flex-col gap-4 p-6 text-sm">
<p class="text-muted-foreground">
Ticket creation needs at least one type and one
priority.
priority, plus one backlog status.
</p>
<div>
<Button as-child variant="outline">
@@ -253,8 +305,8 @@ const hasTicketMetadata =
<CardHeader class="border-b">
<CardTitle>Backlog tickets</CardTitle>
<CardDescription>
Tickets remain here until they are moved into a board
column.
Tickets remain here while their status belongs to
backlog.
</CardDescription>
</CardHeader>
@@ -277,6 +329,9 @@ const hasTicketMetadata =
<th class="px-4 py-3 font-medium">Key</th>
<th class="px-4 py-3 font-medium">Title</th>
<th class="px-4 py-3 font-medium">Type</th>
<th class="px-4 py-3 font-medium">
Status
</th>
<th class="px-4 py-3 font-medium">
Priority
</th>
@@ -284,7 +339,7 @@ const hasTicketMetadata =
v-if="canEditTickets"
class="px-4 py-3 font-medium"
>
Move to board
Move to status
</th>
</tr>
</thead>
@@ -330,12 +385,22 @@ const hasTicketMetadata =
<td class="text-muted-foreground px-4 py-4">
{{ ticket.ticket_type.name }}
</td>
<td class="text-muted-foreground px-4 py-4">
{{
ticketStatusLabel(
ticket.ticket_status,
)
}}
</td>
<td class="text-muted-foreground px-4 py-4">
{{ ticket.ticket_priority.name }}
</td>
<td v-if="canEditTickets" class="px-4 py-4">
<Form
v-if="boardColumns.length > 0"
v-if="
allowedTargetStatuses(ticket)
.length > 0
"
v-bind="
ProjectBoardController.move.form(
{
@@ -353,15 +418,25 @@ const hasTicketMetadata =
value="backlog"
/>
<select
name="board_column_id"
name="ticket_status_id"
:value="
ticket.ticket_status_id ??
''
"
class="border-input bg-background shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 min-w-44 rounded-md border px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px]"
>
<option
v-for="boardColumn in boardColumns"
:key="boardColumn.id"
:value="boardColumn.id"
v-for="ticketStatus in allowedTargetStatuses(
ticket,
)"
:key="ticketStatus.id"
:value="ticketStatus.id"
>
{{ boardColumn.name }}
{{
ticketStatusLabel(
ticketStatus,
)
}}
</option>
</select>
<Button
@@ -377,7 +452,7 @@ const hasTicketMetadata =
v-else
class="text-muted-foreground text-sm"
>
Add board columns first.
No allowed status moves.
</span>
</td>
</tr>