Files
web-frontend/app/pages/notifications.vue

220 lines
7.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { useMutation, useQuery } from '@vue/apollo-composable';
import {
MeDocument,
MyMessengerConnectionsDocument,
MyNotificationHistoryDocument,
SendTestMessengerMessageDocument,
} from '~/composables/graphql/generated';
import { buildMessengerBotStartUrl } from '~/composables/useMessengerBotLink';
const selectedChannel = ref<'TELEGRAM' | 'MAX'>('TELEGRAM');
const customMessage = ref('Тест канала уведомлений Fregat');
const feedback = ref('');
const config = useRuntimeConfig();
const meQuery = useQuery(MeDocument);
const connectionsQuery = useQuery(MyMessengerConnectionsDocument);
const historyQuery = useQuery(MyNotificationHistoryDocument, () => ({
channel: selectedChannel.value,
limit: 50,
}));
const sendTestMutation = useMutation(SendTestMessengerMessageDocument);
watch(selectedChannel, () => {
feedback.value = '';
historyQuery.refetch({
channel: selectedChannel.value,
limit: 50,
});
});
const activeConnection = computed(() =>
connectionsQuery.result.value?.myMessengerConnections?.find(
(item: { type: 'TELEGRAM' | 'MAX'; isActive: boolean; channelId: string }) =>
item.type === selectedChannel.value && item.isActive,
),
);
const telegramConnection = computed(() =>
connectionsQuery.result.value?.myMessengerConnections?.find(
(item: { type: 'TELEGRAM' | 'MAX'; isActive: boolean; channelId: string }) =>
item.type === 'TELEGRAM' && item.isActive,
),
);
const maxConnection = computed(() =>
connectionsQuery.result.value?.myMessengerConnections?.find(
(item: { type: 'TELEGRAM' | 'MAX'; isActive: boolean; channelId: string }) =>
item.type === 'MAX' && item.isActive,
),
);
function buildBotConnectUrl(baseUrl: string) {
const email = meQuery.result.value?.me?.email?.trim().toLowerCase();
if (!email || !baseUrl) {
return '';
}
return buildMessengerBotStartUrl(baseUrl, email);
}
const telegramConnectUrl = computed(() => buildBotConnectUrl(config.public.telegramBotUrl || ''));
const maxConnectUrl = computed(() => buildBotConnectUrl(config.public.maxBotUrl || ''));
async function sendTest() {
feedback.value = '';
if (!activeConnection.value) {
feedback.value = `Канал ${selectedChannel.value} ещё не подключен. Сначала подключите его через бота.`;
return;
}
const result = await sendTestMutation.mutate({
type: selectedChannel.value,
message: customMessage.value || undefined,
});
const payload = result?.data?.sendTestMessengerMessage;
if (!payload) {
feedback.value = 'Не удалось отправить тестовое сообщение.';
return;
}
feedback.value = payload.success
? `Отправлено в ${payload.type}: ${payload.detail}`
: `Ошибка отправки: ${payload.detail}`;
await historyQuery.refetch({
channel: selectedChannel.value,
limit: 50,
});
}
</script>
<template>
<section class="space-y-6">
<div>
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Уведомления</h1>
<p class="mt-1 text-sm text-[#28543f]/80">Управление Telegram и Max в едином стиле кабинета.</p>
</div>
<div class="surface-card rounded-3xl p-5">
<div class="space-y-4">
<h2 class="text-xl font-bold text-[#123824]">Подключение каналов</h2>
<div class="rounded-2xl border border-[#d6ebde] bg-white/75 p-4">
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div>
<p class="font-semibold">Telegram</p>
<p class="text-sm opacity-80">
{{
telegramConnection
? `Подключен: ${telegramConnection.channelId}`
: 'Не подключен'
}}
</p>
</div>
<a
:href="telegramConnectUrl || undefined"
target="_blank"
rel="noopener noreferrer"
class="btn btn-secondary"
:class="{ 'btn-disabled pointer-events-none': !telegramConnectUrl }"
>
{{ telegramConnection ? 'Переподключить Telegram' : 'Подключить Telegram' }}
</a>
</div>
</div>
<div class="rounded-2xl border border-[#d6ebde] bg-white/75 p-4">
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div>
<p class="font-semibold">Max</p>
<p class="text-sm opacity-80">
{{
maxConnection
? `Подключен: ${maxConnection.channelId}`
: 'Не подключен'
}}
</p>
</div>
<a
:href="maxConnectUrl || undefined"
target="_blank"
rel="noopener noreferrer"
class="btn btn-accent"
:class="{ 'btn-disabled pointer-events-none': !maxConnectUrl }"
>
{{ maxConnection ? 'Переподключить Max' : 'Подключить Max' }}
</a>
</div>
</div>
</div>
</div>
<div class="surface-card rounded-3xl p-5">
<div class="space-y-3">
<div class="tabs tabs-boxed w-fit">
<button
class="tab"
:class="{ 'tab-active': selectedChannel === 'TELEGRAM' }"
@click="selectedChannel = 'TELEGRAM'"
>
Telegram
</button>
<button
class="tab"
:class="{ 'tab-active': selectedChannel === 'MAX' }"
@click="selectedChannel = 'MAX'"
>
Max
</button>
</div>
<p class="text-sm opacity-80">
Активный канал:
<span class="font-semibold">
{{ activeConnection ? activeConnection.channelId : 'не подключен' }}
</span>
</p>
<label class="form-control">
<span class="label-text">Тестовое сообщение</span>
<textarea v-model="customMessage" class="textarea textarea-bordered border-[#d0e8d8] bg-white/80" rows="3" />
</label>
<button
class="btn w-fit border-0 bg-[#139957] text-white hover:bg-[#0d854a]"
:disabled="sendTestMutation.loading.value || !activeConnection"
@click="sendTest"
>
Отправить тест
</button>
<div v-if="feedback" class="alert" :class="feedback.startsWith('Ошибка') ? 'alert-error' : 'alert-success'">
{{ feedback }}
</div>
<h2 class="text-xl font-bold text-[#123824]">История по каналу {{ selectedChannel }}</h2>
<div v-if="historyQuery.loading.value" class="alert border-0 bg-white/75">Загрузка истории...</div>
<div v-else-if="(historyQuery.result.value?.myNotificationHistory?.length ?? 0) === 0" class="alert">
История пока пустая.
</div>
<ul v-else class="space-y-3">
<li
v-for="item in historyQuery.result.value?.myNotificationHistory ?? []"
:key="item.id"
class="rounded-xl border border-[#d6ebde] bg-white/75 p-3"
>
<p class="font-semibold">{{ item.title }}</p>
<p class="text-sm opacity-80">{{ item.message }}</p>
<p class="text-xs opacity-60">{{ new Date(item.createdAt).toLocaleString() }}</p>
</li>
</ul>
</div>
</div>
</section>
</template>