Files
KanbanGit/resources/js/components/TicketLinkedTicketsCard.vue
T
2026-04-18 14:04:09 +02:00

159 lines
6.1 KiB
Vue

<script setup lang="ts">
import { Form, Link } from '@inertiajs/vue3';
import ProjectTicketLinkController from '@/actions/App/Http/Controllers/ProjectTicketLinkController';
import InputError from '@/components/InputError.vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Label } from '@/components/ui/label';
import { frostedAsideCardClass, nativeSelectClass } from '@/lib/ticket-styles';
import { show as showTicket } from '@/routes/projects/tickets';
import type { TicketLinkModel } from '@/types/models/ticket-link-model';
import type { TicketLinkTypeModel } from '@/types/models/ticket-link-type-model';
import type { TicketReferenceModel } from '@/types/models/ticket-reference-model';
defineProps<{
canEditTicket: boolean;
linkedTickets: TicketLinkModel[];
projectId: number;
ticketId: number;
ticketLinkOptions: TicketReferenceModel[];
ticketLinkTypes: TicketLinkTypeModel[];
}>();
</script>
<template>
<Card :class="frostedAsideCardClass">
<CardHeader>
<CardTitle>Linked tickets</CardTitle>
<CardDescription>
Track cross-ticket relationships with project-defined link
labels.
</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<Form
v-if="
canEditTicket &&
ticketLinkTypes.length > 0 &&
ticketLinkOptions.length > 0
"
:action="
ProjectTicketLinkController.store.url({
project: projectId,
ticket: ticketId,
})
"
class="border-border grid gap-4 rounded-xl border border-dashed p-4"
method="post"
v-slot="{ errors, processing }"
>
<div class="grid gap-2">
<Label for="ticket_link_type_id">Link type</Label>
<select
id="ticket_link_type_id"
name="ticket_link_type_id"
:class="nativeSelectClass"
>
<option
v-for="ticketLinkType in ticketLinkTypes"
:key="ticketLinkType.id"
:value="ticketLinkType.id"
>
{{ ticketLinkType.label }}
</option>
</select>
<InputError :message="errors.ticket_link_type_id" />
</div>
<div class="grid gap-2">
<Label for="linked_ticket_id">Linked ticket</Label>
<select
id="linked_ticket_id"
name="linked_ticket_id"
:class="nativeSelectClass"
>
<option
v-for="ticketLinkOption in ticketLinkOptions"
:key="ticketLinkOption.id"
:value="ticketLinkOption.id"
>
{{
`${ticketLinkOption.ticket_key} - ${ticketLinkOption.title}`
}}
</option>
</select>
<InputError :message="errors.linked_ticket_id" />
</div>
<Button type="submit" :disabled="processing">Save link</Button>
</Form>
<div
v-if="linkedTickets.length === 0"
class="border-border text-muted-foreground rounded-xl border border-dashed px-4 py-6 text-sm"
>
No ticket links are configured for this ticket yet.
</div>
<div v-else class="space-y-3">
<div
v-for="linkedTicket in linkedTickets"
:key="linkedTicket.id"
class="border-border rounded-xl border px-4 py-4"
>
<div class="flex flex-wrap items-center gap-2">
<Badge variant="secondary">
{{ linkedTicket.ticket_link_type.label }}
</Badge>
<Link
:href="
showTicket({
project: projectId,
ticket: linkedTicket.linked_ticket.id,
})
"
class="text-foreground hover:text-primary font-medium transition"
>
{{ linkedTicket.linked_ticket.ticket_key }}
</Link>
</div>
<p class="text-muted-foreground mt-2 text-sm">
{{ linkedTicket.linked_ticket.title }}
</p>
<Form
v-if="canEditTicket"
:action="
ProjectTicketLinkController.destroy.url({
project: projectId,
ticket: ticketId,
ticketLink: linkedTicket.id,
})
"
class="mt-3 flex justify-end"
method="delete"
v-slot="{ processing: deleting }"
>
<Button
type="submit"
variant="destructive"
size="sm"
:disabled="deleting"
>
Remove
</Button>
</Form>
</div>
</div>
</CardContent>
</Card>
</template>