120 lines
5.3 KiB
Vue
120 lines
5.3 KiB
Vue
<script setup lang="ts">
|
|
import { useLocalization } from '@/composables/useLocalization';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
} from '@/components/ui/card';
|
|
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { UserAvatar } from '@/components/ui/avatar';
|
|
import type { TicketDetailHistoryProps } from '@/types/ticket-detail';
|
|
import type { TranslationKey } from '@/types/localization';
|
|
import type { TicketWorklogEntryType } from '@/types/models/ticket-worklog-entry-model';
|
|
defineProps<TicketDetailHistoryProps>();
|
|
const { t, date } = useLocalization();
|
|
const formatDateTime = (value: string): string =>
|
|
date(new Date(value), { dateStyle: 'medium', timeStyle: 'short' });
|
|
const entryTypeLabels: Record<TicketWorklogEntryType, TranslationKey> = {
|
|
created: 'ticketDetail.historyType.created',
|
|
link_removed: 'ticketDetail.historyType.linkRemoved',
|
|
link_updated: 'ticketDetail.historyType.linkUpdated',
|
|
status_changed: 'ticketDetail.historyType.statusChanged',
|
|
updated: 'ticketDetail.historyType.updated',
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<section
|
|
id="ticket-history"
|
|
aria-labelledby="ticket-history-title"
|
|
class="wrap-anywhere min-w-0 scroll-mt-6"
|
|
>
|
|
<Card class="h-fit">
|
|
<CardHeader>
|
|
<h2 id="ticket-history-title" class="text-lg font-semibold">
|
|
{{ t('ticketDetail.history') }}
|
|
</h2>
|
|
<CardDescription>{{
|
|
t('ticketDetail.historyDescription')
|
|
}}</CardDescription>
|
|
</CardHeader>
|
|
|
|
<CardContent class="space-y-4">
|
|
<div
|
|
v-if="(ticket.worklog_entries?.length ?? 0) === 0"
|
|
class="border-border text-muted-foreground rounded-xl border border-dashed px-4 py-6 text-sm"
|
|
>
|
|
{{ t('ticketDetail.noHistory') }}
|
|
</div>
|
|
|
|
<ol v-else class="relative grid gap-0" role="list">
|
|
<li
|
|
v-for="worklogEntry in ticket.worklog_entries"
|
|
:key="worklogEntry.id"
|
|
class="border-border relative grid min-w-0 grid-cols-[2.5rem_minmax(0,1fr)] gap-3 border-l pb-6 last:border-transparent last:pb-0 sm:grid-cols-[3rem_minmax(0,1fr)] sm:gap-4"
|
|
>
|
|
<div
|
|
class="bg-card relative -left-px flex size-10 -translate-x-1/2 items-center justify-center rounded-full sm:size-12"
|
|
>
|
|
<UserAvatar
|
|
v-if="worklogEntry.actor"
|
|
:user="worklogEntry.actor"
|
|
size="lg"
|
|
/>
|
|
<span
|
|
v-else
|
|
aria-hidden="true"
|
|
class="bg-muted text-muted-foreground flex size-10 items-center justify-center rounded-full border text-sm font-semibold"
|
|
>
|
|
S
|
|
</span>
|
|
</div>
|
|
<article
|
|
class="bg-muted/20 min-w-0 rounded-xl border p-4"
|
|
>
|
|
<div
|
|
class="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-start sm:justify-between"
|
|
>
|
|
<div
|
|
class="flex min-w-0 flex-wrap items-center gap-2"
|
|
>
|
|
<span
|
|
class="text-foreground text-sm font-semibold"
|
|
>
|
|
{{
|
|
worklogEntry.actor?.name ??
|
|
t('ticketDetail.system')
|
|
}}
|
|
</span>
|
|
<Badge variant="outline">
|
|
{{
|
|
t(
|
|
entryTypeLabels[
|
|
worklogEntry.entry_type
|
|
],
|
|
)
|
|
}}
|
|
</Badge>
|
|
</div>
|
|
<time
|
|
:datetime="worklogEntry.created_at"
|
|
class="text-muted-foreground shrink-0 text-xs"
|
|
>
|
|
{{
|
|
formatDateTime(worklogEntry.created_at)
|
|
}}
|
|
</time>
|
|
</div>
|
|
<p class="text-foreground mt-3 text-sm leading-6">
|
|
{{ worklogEntry.summary }}
|
|
</p>
|
|
</article>
|
|
</li>
|
|
</ol>
|
|
</CardContent>
|
|
</Card>
|
|
</section>
|
|
</template>
|