236 lines
11 KiB
Vue
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>
|