100 lines
4.1 KiB
Vue
100 lines
4.1 KiB
Vue
<script setup lang="ts">
|
|
import { Link } from '@inertiajs/vue3';
|
|
import { ChevronDown, ChevronRight } from 'lucide-vue-next';
|
|
import { reactive } from 'vue';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { useLocalization } from '@/composables/useLocalization';
|
|
import { show as showTicket } from '@/routes/projects/tickets';
|
|
import type { TicketTreeListProps } from '@/types/ticket-tree';
|
|
|
|
defineProps<TicketTreeListProps>();
|
|
|
|
const { t } = useLocalization();
|
|
const collapsedTicketIds = reactive(new Set<number>());
|
|
|
|
function toggleTicket(ticketId: number): void {
|
|
if (collapsedTicketIds.has(ticketId)) {
|
|
collapsedTicketIds.delete(ticketId);
|
|
} else {
|
|
collapsedTicketIds.add(ticketId);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ul class="grid min-w-0 gap-3">
|
|
<li v-for="node in nodes" :key="node.id" class="min-w-0">
|
|
<div class="grid min-w-0 grid-cols-[2.75rem_minmax(0,1fr)] gap-2">
|
|
<div class="flex justify-center pt-1">
|
|
<button
|
|
v-if="node.children.length > 0"
|
|
type="button"
|
|
class="border-border bg-card text-muted-foreground hover:border-primary hover:text-primary focus-visible:ring-ring/35 flex size-10 items-center justify-center rounded-md border transition-colors focus-visible:outline-none focus-visible:ring-2"
|
|
:aria-expanded="!collapsedTicketIds.has(node.id)"
|
|
:aria-controls="`ticket-tree-children-${node.id}`"
|
|
:aria-label="
|
|
t(
|
|
collapsedTicketIds.has(node.id)
|
|
? 'tickets.tree.expand'
|
|
: 'tickets.tree.collapse',
|
|
{ key: node.ticket_key },
|
|
)
|
|
"
|
|
@click="toggleTicket(node.id)"
|
|
>
|
|
<ChevronRight
|
|
v-if="collapsedTicketIds.has(node.id)"
|
|
class="size-4"
|
|
aria-hidden="true"
|
|
/>
|
|
<ChevronDown v-else class="size-4" aria-hidden="true" />
|
|
</button>
|
|
<span
|
|
v-else
|
|
class="bg-border mt-4 size-1.5 rounded-full"
|
|
aria-hidden="true"
|
|
/>
|
|
</div>
|
|
|
|
<Link
|
|
:href="
|
|
showTicket({
|
|
project: projectId,
|
|
ticket: node.id,
|
|
})
|
|
"
|
|
class="bg-card text-card-foreground hover:border-primary focus-visible:ring-ring/35 gap-stack group grid min-w-0 rounded-md border p-4 transition-[border-color,box-shadow] focus-visible:outline-none focus-visible:ring-2"
|
|
>
|
|
<div class="flex min-w-0 flex-wrap items-center gap-2">
|
|
<Badge variant="outline">
|
|
{{ node.ticket_key }}
|
|
</Badge>
|
|
<Badge v-if="node.ticket_type_name" variant="secondary">
|
|
{{ node.ticket_type_name }}
|
|
</Badge>
|
|
</div>
|
|
|
|
<span
|
|
class="text-body group-hover:text-primary wrap-anywhere min-w-0 font-semibold"
|
|
>
|
|
{{ node.title }}
|
|
</span>
|
|
</Link>
|
|
</div>
|
|
|
|
<div
|
|
v-if="
|
|
node.children.length > 0 && !collapsedTicketIds.has(node.id)
|
|
"
|
|
:id="`ticket-tree-children-${node.id}`"
|
|
class="border-border ml-[1.375rem] mt-3 border-l pl-2 sm:pl-4"
|
|
>
|
|
<TicketTreeList
|
|
:nodes="node.children"
|
|
:project-id="projectId"
|
|
/>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</template>
|