codex ticket rework

This commit is contained in:
2026-04-13 01:04:06 +02:00
parent b3f2cc3afd
commit 182ed018d1
45 changed files with 1208 additions and 420 deletions
+23
View File
@@ -182,6 +182,29 @@ const hasTicketMetadata =
/>
</div>
<div class="grid gap-2">
<Label for="assignee_user_id">
Assignee
</Label>
<select
id="assignee_user_id"
name="assignee_user_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 value="">Unassigned</option>
<option
v-for="user in users"
:key="user.id"
:value="user.id"
>
{{ user.name }}
</option>
</select>
<InputError
:message="errors.assignee_user_id"
/>
</div>
<div
class="grid gap-2"
v-if="project.show_story_points"
+60
View File
@@ -66,6 +66,12 @@ const linkedRepositoryConnections = computed<RepositoryConnectionModel[]>(() =>
const canCreateBranches = computed(
() => props.canEditTicket && linkedRepositoryConnections.value.length > 0,
);
const canEditAuthor = computed(
() => props.canEditTicket && props.canManageProject,
);
const canViewAuthor = computed(
() => props.project.show_author || props.canManageProject,
);
const defaultBranchRepositoryConnectionId = computed(() =>
String(
@@ -97,6 +103,8 @@ const breadcrumbs: BreadcrumbItem[] = [
const form = useForm({
acceptance_criteria: props.ticket.acceptance_criteria ?? '',
assignee_user_id: props.ticket.assignee_user_id ?? '',
author_user_id: props.ticket.author_user_id ?? '',
blocked_comment: props.ticket.blocked_comment ?? '',
description: props.ticket.description ?? '',
field_values: props.ticketFieldDefinitions.map((ticketFieldDefinition) => {
@@ -461,6 +469,58 @@ const formatDateTime = (value: string): string => {
</div>
</div>
<div class="grid gap-2 md:grid-cols-2 md:gap-6">
<div class="grid gap-2">
<Label for="assignee_user_id">
Assignee
</Label>
<select
id="assignee_user_id"
v-model="form.assignee_user_id"
:disabled="
!canEditTicket || form.processing
"
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] disabled:cursor-not-allowed disabled:opacity-60"
>
<option value="">Unassigned</option>
<option
v-for="user in users"
:key="user.id"
:value="user.id"
>
{{ user.name }}
</option>
</select>
<InputError
:message="form.errors.assignee_user_id"
/>
</div>
<div v-if="canViewAuthor" class="grid gap-2">
<Label for="author_user_id">Author</Label>
<select
id="author_user_id"
v-model="form.author_user_id"
:disabled="
!canEditAuthor || form.processing
"
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] disabled:cursor-not-allowed disabled:opacity-60"
>
<option value="">No author</option>
<option
v-for="user in users"
:key="user.id"
:value="user.id"
>
{{ user.name }}
</option>
</select>
<InputError
:message="form.errors.author_user_id"
/>
</div>
</div>
<div
class="grid gap-2"
v-if="project.show_story_points"
@@ -44,6 +44,9 @@ const breadcrumbs: BreadcrumbItem[] = [
href: metadataSettings(props.project.id),
},
];
const replacementTicketTypes = (ticketTypeId: number) =>
props.ticketTypes.filter((ticketType) => ticketType.id !== ticketTypeId);
</script>
<template>
@@ -133,6 +136,18 @@ const breadcrumbs: BreadcrumbItem[] = [
<InputError :message="errors.show_story_points" />
<Label for="enable-author">
<Checkbox
id="enable-author"
name="show_author"
:default-value="project.show_author"
value="1"
/>
Show author on ticket details
</Label>
<InputError :message="errors.show_author" />
<Button type="submit" :disabled="processing">
Save
</Button>
@@ -171,7 +186,7 @@ const breadcrumbs: BreadcrumbItem[] = [
name="is_parent"
value="1"
/>
Ticket type is can be a parent
Ticket type can act as a parent
</Label>
<InputError :message="errors.is_parent" />
</div>
@@ -196,6 +211,20 @@ const breadcrumbs: BreadcrumbItem[] = [
v-slot="{ errors, processing }"
>
<div class="grid gap-2">
<div
v-if="ticketType.is_parent"
class="flex flex-wrap items-center gap-2"
>
<Badge variant="secondary">
Parent type
</Badge>
<span
class="text-muted-foreground text-xs"
>
This flag is fixed after creation.
</span>
</div>
<Label :for="`type-name-${ticketType.id}`">
Name
</Label>
@@ -206,21 +235,6 @@ const breadcrumbs: BreadcrumbItem[] = [
required
/>
<InputError :message="errors.name" />
<Label
:for="`type-is-parent-${ticketType.id}}`"
>
<Checkbox
:id="`type-is-parent-${ticketType.id}}`"
name="is_parent"
:default-value="
ticketType.is_parent
"
value="1"
/>
Ticket type is can be a parent
</Label>
<InputError :message="errors.is_parent" />
</div>
<div class="grid gap-2">
@@ -260,16 +274,71 @@ const breadcrumbs: BreadcrumbItem[] = [
[project.id, ticketType.id],
)
"
class="mt-3 flex justify-end"
v-slot="{ processing: deleting }"
class="mt-3 grid gap-3"
v-slot="{ errors, processing: deleting }"
>
<Button
type="submit"
variant="destructive"
:disabled="deleting"
<div
v-if="
(ticketType.tickets_count ?? 0) > 0 &&
ticketTypes.length > 1
"
class="grid gap-2"
>
Delete
</Button>
<Label
:for="`replacement-type-${ticketType.id}`"
>
Replacement type for
{{ ticketType.tickets_count }} assigned
ticket{{
ticketType.tickets_count === 1
? ''
: 's'
}}
</Label>
<select
:id="`replacement-type-${ticketType.id}`"
name="replacement_ticket_type_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]"
required
>
<option value="">Choose type</option>
<option
v-for="replacementTicketType in replacementTicketTypes(
ticketType.id,
)"
:key="replacementTicketType.id"
:value="replacementTicketType.id"
>
{{ replacementTicketType.name }}
</option>
</select>
<InputError
:message="
errors.replacement_ticket_type_id
"
/>
</div>
<div
class="flex flex-wrap items-center justify-between gap-3"
>
<p
v-if="
(ticketType.tickets_count ?? 0) > 0
"
class="text-muted-foreground text-sm"
>
Assigned tickets will be moved before
this type is deleted.
</p>
<Button
type="submit"
variant="destructive"
:disabled="deleting"
>
Delete
</Button>
</div>
</Form>
</div>
</CardContent>
@@ -2,6 +2,7 @@ export type ProjectModel = {
id: number;
name: string;
key: string;
show_author: boolean;
show_story_points: boolean;
created_at: string;
updated_at: string;
@@ -2,10 +2,15 @@ import type { TicketFieldValueModel } from '@/types/models/ticket-field-value-mo
import type { TicketPriorityModel } from '@/types/models/ticket-priority-model';
import type { TicketTypeModel } from '@/types/models/ticket-type-model';
import type { TicketWorklogEntryModel } from '@/types/models/ticket-worklog-entry-model';
import type { UserModel } from '@/types/models/user-model';
export type TicketModel = {
id: number;
acceptance_criteria: string | null;
assignee: UserModel | null;
assignee_user_id: number | null;
author: UserModel | null;
author_user_id: number | null;
board_column_id: number | null;
blocked_comment: string | null;
description: string | null;
@@ -4,6 +4,7 @@ export type TicketTypeModel = {
name: string;
position: number;
project_id: number;
tickets_count?: number;
created_at: string;
updated_at: string;
};
+3 -1
View File
@@ -83,7 +83,7 @@ export type TicketFieldTypeOption = {
export type ProjectTicketMetadataSettingsPageProps = {
fieldTypeOptions: TicketFieldTypeOption[];
project: ProjectModel;
error?: string | null;
error?: boolean;
status?: string | null;
ticketFieldDefinitions: TicketFieldDefinitionModel[];
ticketLinkTypes: TicketLinkTypeModel[];
@@ -107,10 +107,12 @@ export type ProjectBacklogPageProps = {
ticketPriorities: TicketPriorityModel[];
tickets: TicketModel[];
ticketTypes: TicketTypeModel[];
users: UserModel[];
};
export type ProjectTicketPageProps = {
canEditTicket: boolean;
canManageProject: boolean;
childTicketTree: TicketTreeNodeModel[];
linkedTickets: TicketLinkModel[];
parentTicketOptions: TicketReferenceModel[];