228 lines
10 KiB
Vue
228 lines
10 KiB
Vue
<script setup lang="ts">
|
|
import { Head } from '@inertiajs/vue3';
|
|
import { Bell, Pencil, Plus, ShieldCheck, Trash2 } from 'lucide-vue-next';
|
|
import { computed, ref } from 'vue';
|
|
import FeedbackState from '@/components/FeedbackState.vue';
|
|
import NotificationEditorDialog from '@/components/projects/NotificationEditorDialog.vue';
|
|
import TemplateInheritancePanel from '@/components/projects/TemplateInheritancePanel.vue';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { notificationLabel } from '@/lib/notification-labels';
|
|
import { useLocalization } from '@/composables/useLocalization';
|
|
import AppLayout from '@/layouts/AppLayout.vue';
|
|
import SettingsLayout from '@/layouts/projects/SettingsLayout.vue';
|
|
import { index, show } from '@/routes/projects';
|
|
import { notifications } from '@/routes/projects/settings';
|
|
import type {
|
|
BreadcrumbItem,
|
|
ProjectNotificationSettingsPageProps,
|
|
} from '@/types';
|
|
import type { NotificationActionModel } from '@/types/models/notification-action-model';
|
|
import type { NotificationEditorTarget } from '@/types/project-notifications';
|
|
|
|
const props = defineProps<ProjectNotificationSettingsPageProps>();
|
|
const { t, date: formatDate } = useLocalization();
|
|
const target = ref<NotificationEditorTarget | null>(null);
|
|
const breadcrumbs = computed<BreadcrumbItem[]>(() => [
|
|
{ title: t('navigation.projects'), href: index() },
|
|
{ title: props.project.name, href: show(props.project.id) },
|
|
{ title: t('notifications.title'), href: notifications(props.project.id) },
|
|
]);
|
|
function timestamp(entry: NotificationActionModel): string {
|
|
const date = entry.updated_at ?? entry.created_at;
|
|
if (!date) {
|
|
return t('notifications.noChanges');
|
|
}
|
|
return t(
|
|
entry.created_at !== date
|
|
? 'notifications.updatedAt'
|
|
: 'notifications.createdAt',
|
|
{
|
|
date: formatDate(new Date(date), {
|
|
dateStyle: 'medium',
|
|
timeStyle: 'short',
|
|
}),
|
|
},
|
|
);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<AppLayout :breadcrumbs="breadcrumbs">
|
|
<Head
|
|
:title="t('notifications.pageTitle', { project: project.name })"
|
|
/>
|
|
<SettingsLayout :project-name="project.name" :project-key="project.key">
|
|
<header class="grid gap-3">
|
|
<h2 class="text-title">{{ t('notifications.title') }}</h2>
|
|
<p class="text-muted-foreground">
|
|
{{ t('notifications.description') }}
|
|
</p>
|
|
</header>
|
|
<FeedbackState
|
|
v-if="status"
|
|
state="success"
|
|
:title="t('notifications.saved')"
|
|
/>
|
|
<TemplateInheritancePanel
|
|
:areas="['notification_actions']"
|
|
:project-id="project.id"
|
|
:template-state="templateState"
|
|
/>
|
|
<section
|
|
class="grid min-w-0 gap-4"
|
|
aria-labelledby="notification-actions-title"
|
|
>
|
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
<h3 id="notification-actions-title" class="text-title">
|
|
{{ t('notifications.actions') }}
|
|
<span class="text-muted-foreground text-sm"
|
|
>({{ notificationActions.length }})</span
|
|
>
|
|
</h3>
|
|
<Button @click="target = { action: 'create' }">
|
|
<Plus class="size-4" aria-hidden="true" />{{
|
|
t('notifications.create')
|
|
}}
|
|
</Button>
|
|
</div>
|
|
<div
|
|
v-if="notificationActions.length === 0"
|
|
class="bg-muted/25 grid justify-items-center gap-3 rounded-xl border border-dashed p-8 text-center"
|
|
>
|
|
<Bell class="text-primary size-8" aria-hidden="true" />
|
|
<h4 class="font-semibold">
|
|
{{ t('notifications.empty') }}
|
|
</h4>
|
|
<p class="text-muted-foreground text-sm">
|
|
{{ t('notifications.emptyHelp') }}
|
|
</p>
|
|
</div>
|
|
<ul v-else class="grid min-w-0 gap-3">
|
|
<li
|
|
v-for="entry in notificationActions"
|
|
:key="entry.id"
|
|
:data-notification="entry.id"
|
|
class="bg-card grid min-w-0 gap-4 rounded-xl border p-4"
|
|
>
|
|
<div
|
|
class="flex flex-wrap items-start justify-between gap-3"
|
|
>
|
|
<div class="grid min-w-0 flex-1 gap-2">
|
|
<h4 class="break-words font-semibold">
|
|
{{ entry.name }}
|
|
</h4>
|
|
<div class="flex flex-wrap gap-2">
|
|
<Badge
|
|
:variant="
|
|
entry.enabled
|
|
? 'default'
|
|
: 'secondary'
|
|
"
|
|
>{{
|
|
t(
|
|
entry.enabled
|
|
? 'notifications.enabled'
|
|
: 'notifications.disabled',
|
|
)
|
|
}}</Badge
|
|
>
|
|
<Badge variant="outline">{{
|
|
notificationLabel(
|
|
t,
|
|
'channel',
|
|
entry.channel,
|
|
entry.channel_label,
|
|
)
|
|
}}</Badge>
|
|
<Badge
|
|
variant="outline"
|
|
class="whitespace-normal"
|
|
>{{
|
|
notificationLabel(
|
|
t,
|
|
'event',
|
|
entry.event_type,
|
|
entry.event_type_label,
|
|
)
|
|
}}</Badge
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-1">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
:aria-label="
|
|
t('notifications.editNamed', {
|
|
name: entry.name,
|
|
})
|
|
"
|
|
@click="target = { action: 'edit', entry }"
|
|
><Pencil class="size-4" aria-hidden="true"
|
|
/></Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
:aria-label="
|
|
t('notifications.deleteNamed', {
|
|
name: entry.name,
|
|
})
|
|
"
|
|
@click="
|
|
target = { action: 'delete', entry }
|
|
"
|
|
><Trash2
|
|
class="text-destructive size-4"
|
|
aria-hidden="true"
|
|
/></Button>
|
|
</div>
|
|
</div>
|
|
<dl class="grid min-w-0 gap-3 text-sm sm:grid-cols-2">
|
|
<div class="grid gap-1">
|
|
<dt class="text-muted-foreground">
|
|
{{ t('notifications.delivery') }}
|
|
</dt>
|
|
<dd class="flex items-center gap-2">
|
|
<ShieldCheck
|
|
class="size-4 shrink-0"
|
|
aria-hidden="true"
|
|
/>{{
|
|
t(
|
|
entry.has_delivery_url
|
|
? 'notifications.urlSaved'
|
|
: 'notifications.urlMissing',
|
|
)
|
|
}}
|
|
</dd>
|
|
</div>
|
|
<div class="grid gap-1">
|
|
<dt class="text-muted-foreground">
|
|
{{ t('notifications.filters') }}
|
|
</dt>
|
|
<dd>
|
|
{{
|
|
entry.filters.length
|
|
? t('notifications.filterCount', {
|
|
count: entry.filters.length,
|
|
})
|
|
: t('notifications.noFiltersShort')
|
|
}}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
<p class="text-caption text-muted-foreground">
|
|
{{ timestamp(entry) }}
|
|
</p>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
<NotificationEditorDialog
|
|
:target="target"
|
|
:settings="props"
|
|
@close="target = null"
|
|
/>
|
|
</SettingsLayout>
|
|
</AppLayout>
|
|
</template>
|