130 lines
3.4 KiB
Vue
130 lines
3.4 KiB
Vue
<script setup lang="ts">
|
|
type EmailAddress = {
|
|
name?: string | null
|
|
address?: string | null
|
|
}
|
|
|
|
type LinkedMessage = {
|
|
id: string
|
|
accountId: string
|
|
mailboxPath: string
|
|
subject?: string | null
|
|
from?: EmailAddress[] | null
|
|
to?: EmailAddress[] | null
|
|
preview?: string | null
|
|
receivedAt?: string | null
|
|
sentAt?: string | null
|
|
linkedAt: string
|
|
hasAttachments: boolean
|
|
canOpen: boolean
|
|
}
|
|
|
|
const props = defineProps<{
|
|
entityType: string
|
|
entityId: string | number
|
|
}>()
|
|
|
|
const messages = ref<LinkedMessage[]>([])
|
|
const loading = ref(true)
|
|
const errorMessage = ref("")
|
|
|
|
const formatAddress = (addresses?: EmailAddress[] | null) => {
|
|
const address = addresses?.[0]
|
|
if (!address) return "Unbekannt"
|
|
return address.name || address.address || "Unbekannt"
|
|
}
|
|
|
|
const formatDate = (value?: string | null) => {
|
|
if (!value) return ""
|
|
return new Intl.DateTimeFormat("de-DE", {
|
|
dateStyle: "medium",
|
|
timeStyle: "short",
|
|
}).format(new Date(value))
|
|
}
|
|
|
|
const openMessage = (message: LinkedMessage) => navigateTo({
|
|
path: "/email",
|
|
query: {
|
|
account: message.accountId,
|
|
mailbox: message.mailboxPath,
|
|
message: message.id,
|
|
},
|
|
})
|
|
|
|
async function loadMessages() {
|
|
loading.value = true
|
|
errorMessage.value = ""
|
|
|
|
try {
|
|
messages.value = await useNuxtApp().$api(
|
|
`/api/email/entity-links/${props.entityType}/${props.entityId}`,
|
|
)
|
|
} catch (err: any) {
|
|
errorMessage.value = err?.data?.error || err?.message || "Verknüpfte E-Mails konnten nicht geladen werden."
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
watch(() => [props.entityType, props.entityId], loadMessages, { immediate: true })
|
|
</script>
|
|
|
|
<template>
|
|
<div class="space-y-3">
|
|
<div v-if="loading" class="space-y-3">
|
|
<USkeleton v-for="index in 4" :key="index" class="h-28" />
|
|
</div>
|
|
|
|
<UAlert
|
|
v-else-if="errorMessage"
|
|
color="error"
|
|
icon="i-heroicons-exclamation-triangle"
|
|
title="E-Mails konnten nicht geladen werden"
|
|
:description="errorMessage"
|
|
/>
|
|
|
|
<TableEmptyState
|
|
v-else-if="!messages.length"
|
|
label="Noch keine E-Mails mit dieser Entität verknüpft"
|
|
/>
|
|
|
|
<article
|
|
v-for="message in messages"
|
|
v-else
|
|
:key="message.id"
|
|
class="rounded-lg border border-(--ui-border) bg-(--ui-bg) p-4"
|
|
>
|
|
<div class="flex items-start justify-between gap-4">
|
|
<div class="min-w-0">
|
|
<h3 class="truncate font-medium text-highlighted">
|
|
{{ message.subject || '(kein Betreff)' }}
|
|
</h3>
|
|
<p class="mt-1 text-sm text-dimmed">
|
|
Von {{ formatAddress(message.from) }} · {{ formatDate(message.receivedAt || message.sentAt) }}
|
|
</p>
|
|
</div>
|
|
<UButton
|
|
v-if="message.canOpen"
|
|
icon="i-heroicons-arrow-top-right-on-square"
|
|
color="neutral"
|
|
variant="soft"
|
|
size="sm"
|
|
@click="openMessage(message)"
|
|
>
|
|
Im Postfach öffnen
|
|
</UButton>
|
|
</div>
|
|
<p class="mt-3 line-clamp-2 text-sm text-muted">
|
|
{{ message.preview || 'Keine Vorschau verfügbar' }}
|
|
</p>
|
|
<div class="mt-3 flex items-center gap-3 text-xs text-dimmed">
|
|
<span>Verknüpft am {{ formatDate(message.linkedAt) }}</span>
|
|
<span v-if="message.hasAttachments" class="flex items-center gap-1">
|
|
<UIcon name="i-heroicons-paper-clip" class="size-3.5" />
|
|
Anhänge
|
|
</span>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</template>
|