feat: добавил личные уведомления для пользователей
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 9s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 26s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 19s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 8s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 9s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 26s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 19s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 8s
Реализовал хранение, получение и отметку прочитанными пользовательских уведомлений. Обновил фронтенд для отображения и управления уведомлениями в профиле студента.
This commit is contained in:
@@ -16,6 +16,7 @@ import type {
|
||||
UserAchievementDto,
|
||||
UserDto,
|
||||
UserQuery,
|
||||
UserNotificationDto,
|
||||
UserStatsDto,
|
||||
} from './types'
|
||||
|
||||
@@ -85,6 +86,14 @@ export const achievementsApi = {
|
||||
},
|
||||
}
|
||||
|
||||
export const notificationsApi = {
|
||||
async list() {
|
||||
const payload = await apiRequest<PagedResult<UserNotificationDto> | UserNotificationDto[]>('/notifications')
|
||||
return extractItems(payload)
|
||||
},
|
||||
markAllRead: () => apiRequest<void>('/notifications/read-all', { method: 'PATCH' }),
|
||||
}
|
||||
|
||||
export const reviewsApi = {
|
||||
create: (lectureId: string | number, rating: 'Like' | 'Neutral' | 'Dislike', text: string) =>
|
||||
apiRequest<ReviewDto>('/reviews', {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Achievement, CoinTransaction, Lecture, Review, User, UserRole } from '@/types'
|
||||
import type { Achievement, CoinTransaction, Lecture, Notification, Review, User, UserRole } from '@/types'
|
||||
import type {
|
||||
AchievementDto,
|
||||
CoinTransactionDto,
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
UserDto,
|
||||
UserStatsDto,
|
||||
UserAchievementDto,
|
||||
UserNotificationDto,
|
||||
} from './types'
|
||||
|
||||
export function mapApiRole(role: string | undefined): UserRole {
|
||||
@@ -127,3 +128,14 @@ export function mapApiCoinTransaction(transaction: CoinTransactionDto): CoinTran
|
||||
type: transaction.amount >= 0 ? 'earned' : 'spent',
|
||||
}
|
||||
}
|
||||
|
||||
export function mapApiNotification(notification: UserNotificationDto): Notification {
|
||||
return {
|
||||
id: String(notification.id),
|
||||
type: notification.type,
|
||||
title: notification.title,
|
||||
body: notification.body,
|
||||
read: notification.isRead,
|
||||
createdAt: notification.createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +185,15 @@ export interface CoinTransactionDto {
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface UserNotificationDto {
|
||||
id: number
|
||||
type: 'reminder' | 'schedule-change' | 'achievement' | 'coins' | 'recommendation'
|
||||
title: string
|
||||
body: string
|
||||
isRead: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface LectureQuery {
|
||||
DateFrom?: string
|
||||
DateTo?: string
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { achievementsApi, usersApi } from '@/api'
|
||||
import { mapApiAchievement, mapApiCoinTransaction } from '@/api/mappers'
|
||||
import { achievementsApi, notificationsApi, usersApi } from '@/api'
|
||||
import { mapApiAchievement, mapApiCoinTransaction, mapApiNotification } from '@/api/mappers'
|
||||
import type { Achievement, CoinTransaction, Notification } from '@/types'
|
||||
import { useAuthStore } from './auth'
|
||||
|
||||
@@ -25,7 +25,10 @@ export const useUserStore = defineStore('user', () => {
|
||||
usersApi.achievements(id),
|
||||
usersApi.transactions(id),
|
||||
])
|
||||
const achievementCatalog = await achievementsApi.list()
|
||||
const [achievementCatalog, notificationPayload] = await Promise.all([
|
||||
achievementsApi.list(),
|
||||
notificationsApi.list(),
|
||||
])
|
||||
|
||||
if (auth.user) {
|
||||
auth.setUser({
|
||||
@@ -55,6 +58,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
(a, b) => Number(a.id) - Number(b.id),
|
||||
)
|
||||
coinHistory.value = transactions.map(mapApiCoinTransaction)
|
||||
notifications.value = notificationPayload.map(mapApiNotification)
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : 'Не удалось загрузить данные профиля.'
|
||||
} finally {
|
||||
@@ -62,7 +66,13 @@ export const useUserStore = defineStore('user', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function markAllRead() {
|
||||
async function fetchNotifications() {
|
||||
const payload = await notificationsApi.list()
|
||||
notifications.value = payload.map(mapApiNotification)
|
||||
}
|
||||
|
||||
async function markAllRead() {
|
||||
await notificationsApi.markAllRead()
|
||||
notifications.value.forEach(n => (n.read = true))
|
||||
}
|
||||
|
||||
@@ -75,6 +85,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
loading,
|
||||
error,
|
||||
fetchStudentData,
|
||||
fetchNotifications,
|
||||
markAllRead,
|
||||
unreadCount,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import GlassCard from '@/components/ui/GlassCard.vue'
|
||||
import AppIcon from '@/components/ui/AppIcon.vue'
|
||||
@@ -7,6 +7,10 @@ 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 => {
|
||||
|
||||
Reference in New Issue
Block a user