135 lines
3.1 KiB
Vue
135 lines
3.1 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted } from 'vue'
|
|
import { useUserStore } from '@/stores/user'
|
|
import GlassCard from '@/components/ui/GlassCard.vue'
|
|
import AppIcon from '@/components/ui/AppIcon.vue'
|
|
import EmptyState from '@/components/ui/EmptyState.vue'
|
|
|
|
const userStore = useUserStore()
|
|
|
|
onMounted(() => {
|
|
void userStore.fetchNotifications()
|
|
})
|
|
|
|
const grouped = computed(() => {
|
|
const map: Record<string, typeof userStore.notifications> = {}
|
|
userStore.notifications.forEach((n) => {
|
|
const day = new Date(n.createdAt).toLocaleDateString('ru-RU')
|
|
map[day] = map[day] || []
|
|
map[day].push(n)
|
|
})
|
|
return Object.entries(map)
|
|
})
|
|
|
|
const typeIcon: Record<string, string> = {
|
|
reminder: 'alarm',
|
|
'schedule-change': 'calendar-event',
|
|
achievement: 'trophy',
|
|
coins: 'coin',
|
|
recommendation: 'sparkles',
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="notifications page-content">
|
|
<div class="header">
|
|
<h1 class="page-title">Уведомления</h1>
|
|
<button class="btn-secondary" @click="userStore.markAllRead">
|
|
Отметить все как прочитанные
|
|
</button>
|
|
</div>
|
|
|
|
<div v-if="userStore.notifications.length === 0" class="empty-wrap">
|
|
<EmptyState
|
|
icon="bell"
|
|
title="Нет уведомлений"
|
|
subtitle="Здесь будут появляться важные события и обновления"
|
|
/>
|
|
</div>
|
|
|
|
<div v-else class="notification-groups">
|
|
<GlassCard v-for="[day, items] in grouped" :key="day" class="group">
|
|
<div class="group-title">{{ day }}</div>
|
|
<div class="items">
|
|
<div v-for="n in items" :key="n.id" class="item" :class="{ unread: !n.read }">
|
|
<AppIcon class="icon" :icon="typeIcon[n.type]" :size="20" />
|
|
<div>
|
|
<div class="item-title">{{ n.title }}</div>
|
|
<div class="item-body">{{ n.body }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</GlassCard>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.notifications {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 18px;
|
|
min-height: calc(100vh - var(--topbar-height) - 28px - 80px);
|
|
}
|
|
.header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.header .page-title {
|
|
margin-bottom: 0;
|
|
}
|
|
.notification-groups {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
}
|
|
.group-title {
|
|
font-weight: 700;
|
|
margin-bottom: 10px;
|
|
}
|
|
.items {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
.item {
|
|
display: flex;
|
|
gap: 12px;
|
|
padding: 10px;
|
|
border-radius: var(--radius-sm);
|
|
background: rgba(255, 255, 255, 0.6);
|
|
border: 1px solid var(--color-border-glass);
|
|
}
|
|
.item.unread {
|
|
border-color: rgba(34, 197, 94, 0.4);
|
|
background: rgba(34, 197, 94, 0.08);
|
|
}
|
|
.icon {
|
|
color: var(--color-text);
|
|
flex-shrink: 0;
|
|
}
|
|
.item-title {
|
|
font-weight: 600;
|
|
}
|
|
.item-body {
|
|
font-size: 13px;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.empty-wrap {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.notifications {
|
|
min-height: calc(100vh - var(--topbar-height) - 16px - 80px);
|
|
}
|
|
}
|
|
</style>
|