<script setup>
// Лог доставок: последние N с фильтрами по каналу и статусу.
import { computed, onMounted, ref } from "vue";
import {
GnBadge,
GnIconButton,
GnLoader,
GnPageHeader,
GnSelect,
GnStatusCard,
GnTable,
} from "gnexus-ui-kit/vue";
import { useToast } from "gnexus-ui-kit/vue";
import { api } from "../api.js";
import { t } from "../i18n/index.js";
import { badgeFor, fmtTime, th, truncate } from "../ui.js";
const toast = useToast();
const rows = ref([]);
const loading = ref(true);
const error = ref("");
// computed: t() внутри module-const заморозил бы язык (см. спек i18n)
const CHANNELS = computed(() => [
{ value: "", label: t("deliveries.filterAll") },
{ value: "telegram", label: "telegram" },
{ value: "email", label: "email" },
{ value: "s2s", label: "s2s" },
{ value: "internal_log", label: "internal_log" },
]);
const STATUSES = computed(() => [
{ value: "", label: t("deliveries.filterAll") },
{ value: "pending", label: "pending" },
{ value: "delivered", label: "delivered" },
{ value: "failed", label: "failed" },
{ value: "skipped", label: "skipped" },
]);
const channel = ref("");
const status = ref("");
async function load() {
loading.value = true;
error.value = "";
try {
const query = { limit: 100 };
if (channel.value) query.channel = channel.value;
if (status.value) query.status = status.value;
rows.value = await api.listDeliveries(query);
} catch (ex) {
error.value = ex.message;
toast.error(ex.message);
} finally {
loading.value = false;
}
}
onMounted(load);
</script>
<template>
<main class="container page">
<GnPageHeader
kicker="Synapse"
:title="t('deliveries.title')"
:subtitle="t('deliveries.subtitle')"
/>
<!-- Фильтры отдельным блоком, с подписями -->
<div class="filters">
<GnSelect v-model="status" :label="t('deliveries.filterStatus')" :options="STATUSES" @update:model-value="load" />
<GnSelect v-model="channel" :label="t('deliveries.filterChannel')" :options="CHANNELS" @update:model-value="load" />
<div class="refresh-slot">
<GnIconButton icon="ph-arrow-counter-clockwise" :label="t('deliveries.refresh')" @click="load" />
</div>
</div>
<GnLoader v-if="loading" circle :label="t('deliveries.loading')" />
<GnStatusCard v-else-if="error" variant="error" icon="ph-warning-circle" :text="error" />
<template v-else>
<GnTable
:rows="rows"
:empty-text="t('deliveries.empty')"
:columns="[
{ key: 'created_at', label: t('deliveries.colTime') },
{ key: 'event', label: th(t('deliveries.colEvent'), t('deliveries.colEventHint')) },
{ key: 'channel', label: t('deliveries.colChannel') },
{ key: 'target_name', label: t('deliveries.colTarget') },
{ key: 'status', label: th(t('deliveries.colStatus'), t('deliveries.colStatusHint')) },
{ key: 'attempts', label: th(t('deliveries.colAttempts'), t('deliveries.colAttemptsHint')) },
{ key: 'last_error', label: t('deliveries.colError') },
{ key: 'rendered_message', label: t('deliveries.colMessage') },
]"
>
<template #cell-created_at="{ value }">{{ fmtTime(value) }}</template>
<template #cell-event="{ row }">
<code class="event-id">{{ truncate(row.event_id, 13) }}</code>
</template>
<template #cell-status="{ value }">
<GnBadge :variant="badgeFor(value).variant" outline>{{ value }}</GnBadge>
</template>
<template #cell-attempts="{ value }">{{ value ?? 0 }}</template>
<template #cell-last_error="{ value }">{{ truncate(value, 60) || "—" }}</template>
<template #cell-rendered_message="{ value }">
<span :title="value || ''">{{ truncate(value, 60) || "—" }}</span>
</template>
</GnTable>
</template>
</main>
</template>
<style scoped>
.page {
max-width: 1440px;
margin-inline: auto;
padding-block: var(--gn-space-6);
display: flex;
flex-direction: column;
gap: var(--gn-space-4);
}
.filters {
display: flex;
align-items: flex-end;
gap: var(--gn-space-4);
}
/* Кнопка обновления центрируется точно по селекту: flex-end у .filters
выравнивает по краям отступов (у form-group кита margin-bottom 15px),
поэтому slot поднят тем же отступом + 5px внутреннего запаса группы,
высота = высоте инпута кита (46px), кнопка внутри по центру. */
.refresh-slot {
height: 46px;
display: flex;
align-items: center;
margin-bottom: var(--gn-space-5);
}
.event-id {
font-size: 0.78em;
}
</style>