feat(KAN-30): rebuild responsive ticket detail view
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { UserAvatar } from '@/components/ui/avatar';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import { useLocalization } from '@/composables/useLocalization';
|
||||
import { ticketStatusLabel } from '@/lib/tickets';
|
||||
import type { TicketDetailMetadataProps } from '@/types/ticket-detail';
|
||||
const props = defineProps<TicketDetailMetadataProps>();
|
||||
const { t, date, number } = useLocalization();
|
||||
const fields = computed(() =>
|
||||
props.ticketFieldDefinitions.map((definition) => {
|
||||
const value = props.ticket.field_values?.find(
|
||||
(field) => field.ticket_field_definition_id === definition.id,
|
||||
);
|
||||
return {
|
||||
id: definition.id,
|
||||
label: definition.label,
|
||||
value:
|
||||
definition.field_type === 'user'
|
||||
? (value?.user_value?.name ?? t('tickets.unassigned'))
|
||||
: value?.value_number != null
|
||||
? number(value.value_number)
|
||||
: t('ticketDetail.notSet'),
|
||||
};
|
||||
}),
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<section
|
||||
id="ticket-metadata"
|
||||
aria-labelledby="ticket-metadata-title"
|
||||
class="min-w-0 scroll-mt-6"
|
||||
>
|
||||
<Card class="bg-muted/30">
|
||||
<CardHeader
|
||||
><h2 id="ticket-metadata-title" class="text-lg font-semibold">
|
||||
{{ t('ticketDetail.metadata') }}
|
||||
</h2></CardHeader
|
||||
>
|
||||
<CardContent class="wrap-anywhere flex min-w-0 flex-col gap-6">
|
||||
<dl
|
||||
class="grid min-w-0 gap-5 text-sm sm:grid-cols-2 xl:grid-cols-1"
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<dt class="text-muted-foreground">
|
||||
{{ t('ticketDetail.status') }}
|
||||
</dt>
|
||||
<dd>
|
||||
{{
|
||||
ticketStatusLabel(
|
||||
ticket.ticket_status,
|
||||
t('tickets.noStatus'),
|
||||
)
|
||||
}}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<dt class="text-muted-foreground">
|
||||
{{ t('ticketDetail.type') }}
|
||||
</dt>
|
||||
<dd>{{ ticket.ticket_type.name }}</dd>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<dt class="text-muted-foreground">
|
||||
{{ t('ticketDetail.priority') }}
|
||||
</dt>
|
||||
<dd>
|
||||
<Badge variant="outline">{{
|
||||
ticket.ticket_priority.name
|
||||
}}</Badge>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<dt class="text-muted-foreground">
|
||||
{{ t('ticketDetail.assignee') }}
|
||||
</dt>
|
||||
<dd class="flex items-center gap-2">
|
||||
<UserAvatar
|
||||
v-if="ticket.assignee"
|
||||
:user="ticket.assignee"
|
||||
size="sm"
|
||||
/>{{
|
||||
ticket.assignee?.name ?? t('tickets.unassigned')
|
||||
}}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
v-if="project.show_author || canManageProject"
|
||||
class="space-y-2"
|
||||
>
|
||||
<dt class="text-muted-foreground">
|
||||
{{ t('ticketDetail.author') }}
|
||||
</dt>
|
||||
<dd class="flex items-center gap-2">
|
||||
<UserAvatar
|
||||
v-if="ticket.author"
|
||||
:user="ticket.author"
|
||||
size="sm"
|
||||
/>{{
|
||||
ticket.author?.name ??
|
||||
t('ticketDetail.noAuthor')
|
||||
}}
|
||||
</dd>
|
||||
</div>
|
||||
<div v-if="project.show_story_points" class="space-y-2">
|
||||
<dt class="text-muted-foreground">
|
||||
{{ t('ticketDetail.storyPoints') }}
|
||||
</dt>
|
||||
<dd>
|
||||
{{
|
||||
ticket.story_points != null
|
||||
? number(ticket.story_points)
|
||||
: t('ticketDetail.notSet')
|
||||
}}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
v-for="field in fields"
|
||||
:key="field.id"
|
||||
class="space-y-2"
|
||||
>
|
||||
<dt class="text-muted-foreground">{{ field.label }}</dt>
|
||||
<dd>{{ field.value }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div
|
||||
v-if="ticket.is_blocked"
|
||||
class="border-warning/30 bg-warning/10 space-y-2 rounded-lg border p-4 text-sm"
|
||||
>
|
||||
<p class="font-semibold">{{ t('tickets.blocked') }}</p>
|
||||
<p class="whitespace-pre-wrap">
|
||||
{{ ticket.blocked_comment }}
|
||||
</p>
|
||||
</div>
|
||||
<dl
|
||||
class="text-muted-foreground grid gap-4 border-t pt-5 text-xs"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<dt>{{ t('ticketDetail.created') }}</dt>
|
||||
<dd>
|
||||
<time :datetime="ticket.created_at">{{
|
||||
date(new Date(ticket.created_at), {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
})
|
||||
}}</time>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<dt>{{ t('ticketDetail.updated') }}</dt>
|
||||
<dd>
|
||||
<time :datetime="ticket.updated_at">{{
|
||||
date(new Date(ticket.updated_at), {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
})
|
||||
}}</time>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user