init
This commit is contained in:
134
frontend/pages/index.vue
Normal file
134
frontend/pages/index.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<script setup lang="ts">
|
||||
import Card from "primevue/card";
|
||||
import Message from "primevue/message";
|
||||
import Skeleton from "primevue/skeleton";
|
||||
import Tag from "primevue/tag";
|
||||
import type { ActiveSignalCountByBot, Bot } from "~/types";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
});
|
||||
|
||||
type BotCard = {
|
||||
bot: Bot;
|
||||
totalSignals: number;
|
||||
};
|
||||
|
||||
const { user } = useAuth();
|
||||
|
||||
const loading = ref(true);
|
||||
const loadError = ref("");
|
||||
const botCards = ref<BotCard[]>([]);
|
||||
|
||||
const availableBots = computed(() => user.value?.botAccesses?.map((access) => access.bot) ?? []);
|
||||
|
||||
const formatSignalCount = (count: number) => {
|
||||
if (count === 0) return "Нет активных сигналов";
|
||||
if (count === 1) return "1 активный сигнал";
|
||||
if (count < 5) return `${count} активных сигнала`;
|
||||
return `${count} активных сигналов`;
|
||||
};
|
||||
|
||||
const loadBotCards = async () => {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await useApi<{ items: ActiveSignalCountByBot[] }>("/signals/active-counts");
|
||||
const countsByBotKey = new Map(response.items.map((item) => [item.botKey, item.activeSignals]));
|
||||
const cards = availableBots.value.map((bot) => ({
|
||||
bot,
|
||||
totalSignals: countsByBotKey.get(bot.key) ?? 0
|
||||
}));
|
||||
|
||||
botCards.value = cards;
|
||||
loadError.value = "";
|
||||
} catch (error) {
|
||||
loadError.value = error instanceof Error ? error.message : "Не удалось загрузить список ботов";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await loadBotCards();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => availableBots.value.map((bot) => bot.key).join("|"),
|
||||
(nextKeys, previousKeys) => {
|
||||
if (nextKeys === previousKeys) return;
|
||||
void loadBotCards();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="grid gap-4">
|
||||
<div
|
||||
class="flex flex-col gap-4 rounded-[24px] border p-5 md:flex-row md:items-start md:justify-between"
|
||||
:style="{
|
||||
borderColor: 'var(--border)',
|
||||
background: 'radial-gradient(circle at top right, color-mix(in srgb, var(--accent) 10%, transparent), transparent 28%), var(--surface)',
|
||||
boxShadow: '0 10px 30px color-mix(in srgb, var(--text) 4%, transparent)'
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
<span class="mb-2 inline-block text-xs font-semibold uppercase tracking-[0.18em] text-(--muted)">Доступные боты</span>
|
||||
<h2 class="m-0 text-3xl font-semibold">Выберите бота</h2>
|
||||
<p class="m-0 text-sm leading-6 text-(--muted)">
|
||||
Каждая карточка открывает отдельную ленту сигналов без общего шума и лишних фильтров на старте.
|
||||
</p>
|
||||
</div>
|
||||
<Tag class="rounded" :value="`${availableBots.length} в доступе`" severity="contrast" />
|
||||
</div>
|
||||
|
||||
<Message v-if="loadError" severity="error" :closable="false">{{ loadError }}</Message>
|
||||
<Message v-else-if="!loading && botCards.length === 0" severity="info" :closable="false">
|
||||
У пользователя пока нет доступа ни к одному боту.
|
||||
</Message>
|
||||
|
||||
<div v-if="loading" class="bot-grid">
|
||||
<Card v-for="index in 6" :key="index" class="overflow-hidden rounded-[24px] border shadow-sm">
|
||||
<template #content>
|
||||
<div class="grid gap-3 p-4">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<Skeleton width="2.75rem" height="2.75rem" borderRadius="16px" />
|
||||
<Skeleton width="2.25rem" height="1.75rem" borderRadius="999px" />
|
||||
</div>
|
||||
<Skeleton width="3rem" height="0.7rem" />
|
||||
<Skeleton width="72%" height="1.45rem" />
|
||||
<Skeleton width="58%" height="0.95rem" />
|
||||
<Skeleton width="100%" height="2.5rem" borderRadius="16px" />
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div v-else class="bot-grid">
|
||||
<NuxtLink
|
||||
v-for="card in botCards"
|
||||
:key="card.bot.id"
|
||||
:to="`/bots/${card.bot.key}`"
|
||||
class="bot-tile"
|
||||
>
|
||||
<div class="bot-tile__top">
|
||||
<div class="bot-tile__icon">
|
||||
<AppLogo size="26px" />
|
||||
</div>
|
||||
<Tag class="bot-tile__count" :value="String(card.totalSignals)" severity="contrast" />
|
||||
</div>
|
||||
|
||||
<div class="bot-tile__body">
|
||||
<p class="bot-tile__eyebrow">Бот</p>
|
||||
<h2>{{ card.bot.name }}</h2>
|
||||
<p class="bot-tile__meta">{{ formatSignalCount(card.totalSignals) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="bot-tile__footer">
|
||||
<span class="bot-tile__action">Открыть ленту</span>
|
||||
<i class="pi pi-arrow-right bot-tile__arrow" />
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user