Files
KanbanGit/resources/js/pages/projects/Ticket.vue
T
2026-04-18 14:04:09 +02:00

236 lines
11 KiB
Vue

<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { computed } from 'vue';
import TicketBranchesCard from '@/components/TicketBranchesCard.vue';
import TicketChildTicketsCard from '@/components/TicketChildTicketsCard.vue';
import TicketDetailsCard from '@/components/TicketDetailsCard.vue';
import TicketLinkedTicketsCard from '@/components/TicketLinkedTicketsCard.vue';
import TicketPullRequestsCard from '@/components/TicketPullRequestsCard.vue';
import TicketSnapshotCard from '@/components/TicketSnapshotCard.vue';
import TicketWorklogCard from '@/components/TicketWorklogCard.vue';
import TicketPriorityIcon from '@/components/TicketPriorityIcon.vue';
import TicketTypeIcon from '@/components/TicketTypeIcon.vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import AppLayout from '@/layouts/AppLayout.vue';
import { airyOutlineButtonClass } from '@/lib/ticket-styles';
import { index as backlogIndex } from '@/routes/projects/backlog';
import { index as boardIndex } from '@/routes/projects/board';
import { index as projectIndex, show } from '@/routes/projects/index';
import { show as showTicket } from '@/routes/projects/tickets';
import type { BreadcrumbItem, ProjectTicketPageProps } from '@/types';
const props = defineProps<ProjectTicketPageProps>();
const canEditAuthor = computed(
() => props.canEditTicket && props.canManageProject,
);
const canViewAuthor = computed(
() => props.project.show_author || props.canManageProject,
);
const breadcrumbs: BreadcrumbItem[] = [
{
title: 'Projects',
href: projectIndex(),
},
{
title: props.project.name,
href: show(props.project.id),
},
{
title: props.ticket.ticket_key,
href: showTicket({
project: props.project.id,
ticket: props.ticket.id,
}),
},
];
</script>
<template>
<AppLayout :breadcrumbs="breadcrumbs">
<Head :title="ticket.ticket_key" />
<div class="relative isolate flex flex-1 flex-col gap-8 px-4 py-6">
<div
class="pointer-events-none absolute inset-x-0 top-0 -z-10 h-72 bg-[radial-gradient(circle_at_top_left,rgba(171,246,136,0.34),transparent_42%),radial-gradient(circle_at_top_right,rgba(216,231,203,0.7),transparent_34%)]"
/>
<section
class="rounded-[2rem] border border-white/70 bg-[linear-gradient(135deg,rgba(255,255,255,0.94)_0%,rgba(241,244,245,0.9)_100%)] p-6 shadow-[0_28px_80px_-48px_rgba(45,51,53,0.4)] backdrop-blur-sm dark:border-white/10 dark:bg-[linear-gradient(135deg,rgba(17,20,20,0.92)_0%,rgba(24,28,28,0.88)_100%)] dark:shadow-black/30"
>
<div
class="flex flex-col gap-6 xl:flex-row xl:items-start xl:justify-between"
>
<div class="max-w-3xl space-y-4">
<div class="flex flex-wrap items-center gap-2.5">
<Badge
variant="outline"
class="dark:bg-card/85 dark:text-muted-foreground rounded-full border-white/70 bg-white/80 px-3 py-1 text-[0.68rem] font-semibold uppercase tracking-[0.2em] text-[#5a6062] shadow-[0_16px_40px_-34px_rgba(45,51,53,0.3)] dark:border-white/10"
>
{{ ticket.ticket_key }}
</Badge>
<Badge
variant="outline"
class="dark:bg-card/80 dark:text-foreground gap-2 rounded-full border-white/70 bg-white/70 px-3 py-1.5 text-[#2d3335] shadow-[0_16px_40px_-34px_rgba(45,51,53,0.3)] dark:border-white/10"
>
<TicketTypeIcon class="size-5" />
{{ ticket.ticket_type.name }}
</Badge>
<Badge
variant="outline"
class="dark:bg-card/80 dark:text-foreground gap-2 rounded-full border-white/70 bg-white/70 px-3 py-1.5 text-[#2d3335] shadow-[0_16px_40px_-34px_rgba(45,51,53,0.3)] dark:border-white/10"
>
<TicketPriorityIcon class="size-5" />
{{ ticket.ticket_priority.name }}
</Badge>
<Badge
v-if="ticket.ticket_status"
variant="outline"
class="dark:bg-card/80 dark:text-foreground rounded-full border-white/70 bg-white/70 px-3 py-1.5 text-[#2d3335] shadow-[0_16px_40px_-34px_rgba(45,51,53,0.3)] dark:border-white/10"
>
{{ ticket.ticket_status.name }}
</Badge>
<Badge
v-if="ticket.is_blocked"
class="rounded-full border-transparent bg-[#fdf6b0] px-3 py-1.5 text-[#615d27] dark:bg-[#65612a]/35 dark:text-[#fdf6b0]"
>
Blocked
</Badge>
<Badge
v-if="!canEditTicket"
variant="outline"
class="dark:bg-card/80 dark:text-muted-foreground rounded-full border-white/70 bg-white/70 px-3 py-1.5 text-[#5a6062] shadow-[0_16px_40px_-34px_rgba(45,51,53,0.3)] dark:border-white/10"
>
Read only
</Badge>
</div>
<div class="space-y-3">
<p
class="dark:text-muted-foreground text-[0.72rem] font-semibold uppercase tracking-[0.22em] text-[#5a6062]"
>
Ticket detail
</p>
<h1
class="font-display dark:text-foreground text-3xl font-semibold tracking-tight text-[#2d3335] md:text-4xl"
>
{{ ticket.title }}
</h1>
<p
class="dark:text-muted-foreground max-w-2xl text-sm leading-7 text-[#5a6062]"
>
Review ticket details, update tracked fields,
and inspect the change timeline in the new
Stitch-inspired workspace.
</p>
</div>
</div>
<div class="flex flex-wrap gap-3">
<Button
as-child
variant="outline"
:class="airyOutlineButtonClass"
>
<Link :href="backlogIndex(project.id)">
Open backlog
</Link>
</Button>
<Button
as-child
variant="outline"
:class="airyOutlineButtonClass"
>
<Link :href="boardIndex(project.id)">
Open board
</Link>
</Button>
<Button
as-child
variant="outline"
:class="airyOutlineButtonClass"
>
<Link :href="show(project.id)">
Back to project
</Link>
</Button>
</div>
</div>
</section>
<div
v-if="status"
class="rounded-[1.5rem] border border-emerald-200/80 bg-emerald-50/80 px-5 py-4 text-sm text-emerald-900 shadow-[0_20px_45px_-38px_rgba(16,185,129,0.45)] dark:border-emerald-900/60 dark:bg-emerald-950/30 dark:text-emerald-100"
>
{{ status }}
</div>
<div
class="grid gap-6 xl:grid-cols-[minmax(0,1.5fr)_minmax(320px,0.95fr)] 2xl:grid-cols-[minmax(0,1.35fr)_minmax(320px,0.95fr)_minmax(280px,0.85fr)]"
>
<TicketDetailsCard
:can-edit-author="canEditAuthor"
:can-edit-ticket="canEditTicket"
:can-view-author="canViewAuthor"
:parent-ticket-options="parentTicketOptions"
:project="project"
:ticket="ticket"
:ticket-field-definitions="ticketFieldDefinitions"
:ticket-priorities="ticketPriorities"
:ticket-types="ticketTypes"
:users="users"
/>
<div class="space-y-6">
<TicketBranchesCard
:can-edit-ticket="canEditTicket"
:project-id="project.id"
:repository-branches="repositoryBranches"
:suggested-branch-name="suggestedBranchName"
:ticket-id="ticket.id"
/>
<TicketLinkedTicketsCard
:can-edit-ticket="canEditTicket"
:linked-tickets="linkedTickets"
:project-id="project.id"
:ticket-id="ticket.id"
:ticket-link-options="ticketLinkOptions"
:ticket-link-types="ticketLinkTypes"
/>
<TicketPullRequestsCard
:repository-pull-requests="repositoryPullRequests"
/>
<TicketChildTicketsCard
:child-ticket-tree="childTicketTree"
:project-id="project.id"
/>
<TicketWorklogCard
:worklog-entries="ticket.worklog_entries ?? []"
/>
</div>
<div class="2xl:sticky 2xl:top-24 2xl:self-start">
<TicketSnapshotCard
:repository-branches="repositoryBranches"
:repository-pull-requests="repositoryPullRequests"
:ticket="ticket"
/>
</div>
</div>
</div>
</AppLayout>
</template>