b75c0b4f2b
renamed: serv_nginx/bbvue/src/stores/user.js -> serv_nginx/bbvue/src/stores/user_store.js modified: serv_nginx/bbvue/src/views/Members.vue search-input width is corrected
379 lines
12 KiB
JavaScript
379 lines
12 KiB
JavaScript
// stores/user.js
|
|
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
import { apiClient, handleApiError } from './helpers/api'
|
|
|
|
export const useUserStore = defineStore('user', () => {
|
|
// State
|
|
const userStats = ref(null)
|
|
const userTraining = ref(null)
|
|
const userAchievements = ref([])
|
|
const personalBests = ref([])
|
|
const upcomingEvents = ref([])
|
|
const currentTrainingPlan = ref(null)
|
|
const workoutHistory = ref([])
|
|
const loading = ref(false)
|
|
const error = ref('')
|
|
|
|
// Getters
|
|
const completedAchievements = computed(() =>
|
|
userAchievements.value.filter(achievement => achievement.verified || achievement.achieved)
|
|
)
|
|
|
|
const pendingAchievements = computed(() =>
|
|
userAchievements.value.filter(achievement => !(achievement.verified || achievement.achieved))
|
|
)
|
|
|
|
const achievementProgress = computed(() => {
|
|
if (!userAchievements.value.length) return 0
|
|
return Math.round((completedAchievements.value.length / userAchievements.value.length) * 100)
|
|
})
|
|
|
|
const verifiedPersonalBests = computed(() =>
|
|
personalBests.value.filter(best => best.verified)
|
|
)
|
|
|
|
const confirmedEvents = computed(() =>
|
|
upcomingEvents.value.filter(event => event.registrationStatus === 'confirmed')
|
|
)
|
|
|
|
const totalWorkouts = computed(() =>
|
|
userStats.value?.workoutsCount || workoutHistory.value.length
|
|
)
|
|
|
|
const totalCalories = computed(() =>
|
|
workoutHistory.value.reduce((sum, workout) => sum + (workout.calories || 0), 0)
|
|
)
|
|
|
|
// Вспомогательная функция для обработки loading/error
|
|
const withStoreLoading = async (fn) => {
|
|
loading.value = true
|
|
error.value = ''
|
|
try {
|
|
return await fn()
|
|
} catch (err) {
|
|
const result = handleApiError(err)
|
|
error.value = result.error
|
|
return result
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// Actions - Основные данные пользователя
|
|
const fetchUserStats = async () => {
|
|
return withStoreLoading(async () => {
|
|
try {
|
|
const response = await apiClient.get('/user/stats')
|
|
console.log("debug /user/stats " + response.data)
|
|
userStats.value = response.data
|
|
return { success: true, data: userStats.value }
|
|
} catch (error) {
|
|
// Fallback на мок данные если endpoint не готов
|
|
console.warn('Stats endpoint not available, using mock data', error)
|
|
userStats.value = {
|
|
totalDistance: 245.5,
|
|
totalTime: 12540,
|
|
avgPace: '5:15',
|
|
workoutsCount: 36,
|
|
currentStreak: 7,
|
|
longestStreak: 21,
|
|
weeklyDistance: 25.8,
|
|
monthlyDistance: 98.2,
|
|
personal_bests: {
|
|
best_5k: '23:45',
|
|
best_10k: '48:15',
|
|
best_half: '1:48:30',
|
|
best_marathon: null
|
|
}
|
|
}
|
|
return { success: true, data: userStats.value }
|
|
}
|
|
})
|
|
}
|
|
|
|
const fetchUserAchievements = async () => {
|
|
return withStoreLoading(async () => {
|
|
try {
|
|
const response = await apiClient.get('/user/achievements')
|
|
console.log("debug /user/achievements " + response.data)
|
|
userAchievements.value = response.data || []
|
|
return { success: true, data: userAchievements.value }
|
|
} catch (error) {
|
|
console.warn('Achievements endpoint not available, using mock data', error)
|
|
userAchievements.value = [
|
|
{
|
|
id: 1,
|
|
type: 'distance',
|
|
title: 'Первый забег',
|
|
description: 'Пробежать первую 5км',
|
|
verified: true,
|
|
date: '2024-01-20',
|
|
badgeImage: '/badges/first-run.png'
|
|
},
|
|
{
|
|
id: 2,
|
|
type: 'consistency',
|
|
title: 'Неделя тренировок',
|
|
description: 'Тренироваться 7 дней подряд',
|
|
verified: true,
|
|
date: '2024-02-15',
|
|
badgeImage: '/badges/week-streak.png'
|
|
},
|
|
{
|
|
id: 3,
|
|
type: 'distance',
|
|
title: '100 км',
|
|
description: 'Пробежать 100 км',
|
|
verified: true,
|
|
date: '2024-03-01',
|
|
badgeImage: '/badges/100km.png'
|
|
},
|
|
{
|
|
id: 4,
|
|
type: 'distance',
|
|
title: 'Полумарафон',
|
|
description: 'Пробежать 21.1 км',
|
|
verified: false,
|
|
badgeImage: '/badges/half-marathon.png'
|
|
},
|
|
{
|
|
id: 5,
|
|
type: 'speed',
|
|
title: 'Скорость',
|
|
description: 'Пробежать 5км быстрее 25 минут',
|
|
verified: false,
|
|
badgeImage: '/badges/speedster.png'
|
|
}
|
|
]
|
|
return { success: true, data: userAchievements.value }
|
|
}
|
|
})
|
|
}
|
|
|
|
// Новые actions для дополнительных данных
|
|
const fetchPersonalBests = async () => {
|
|
return withStoreLoading(async () => {
|
|
try {
|
|
const response = await apiClient.get('/user/personal-bests')
|
|
console.log("debug /user/personal-bests " + response.data)
|
|
personalBests.value = response.data
|
|
return { success: true, data: personalBests.value }
|
|
} catch (error) {
|
|
console.warn('Personal bests 1endpoint not available, using mock data', error)
|
|
personalBests.value = []
|
|
return { success: true, data: personalBests.value }
|
|
}
|
|
})
|
|
}
|
|
|
|
const fetchUpcomingEvents = async () => {
|
|
return withStoreLoading(async () => {
|
|
try {
|
|
const response = await apiClient.get('/events/upcoming')
|
|
console.log("debug /events/upcoming " + response.data)
|
|
upcomingEvents.value = response.data
|
|
return { success: true, data: upcomingEvents.value }
|
|
} catch (error) {
|
|
console.warn('Events endpoint not available, using mock data', error)
|
|
upcomingEvents.value = [
|
|
{
|
|
id: 1,
|
|
title: 'Летний забег 10км',
|
|
date: '2024-06-15T09:00:00',
|
|
location: 'Городской парк',
|
|
type: 'race',
|
|
distance: '10 км',
|
|
registrationStatus: 'confirmed'
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'Тренировка для начинающих',
|
|
date: '2024-06-20T18:30:00',
|
|
location: 'Стадион "Спартак"',
|
|
type: 'training',
|
|
registrationStatus: 'confirmed'
|
|
}
|
|
]
|
|
return { success: true, data: upcomingEvents.value }
|
|
}
|
|
})
|
|
}
|
|
|
|
const fetchCurrentTrainingPlan = async () => {
|
|
return withStoreLoading(async () => {
|
|
try {
|
|
const response = await apiClient.get('/user/training-plans')
|
|
console.log("debug /user/training-plans/active " + response.data)
|
|
currentTrainingPlan.value = response.data
|
|
return { success: true, data: currentTrainingPlan.value }
|
|
} catch (error) {
|
|
console.warn('Training plan endpoint not available, using mock data', error)
|
|
currentTrainingPlan.value = {
|
|
id: 1,
|
|
title: 'Подготовка к полумарафону',
|
|
description: '12-недельный план подготовки к первому полумарафону',
|
|
weeks: 12,
|
|
currentWeek: 4,
|
|
workoutsPerWeek: 3,
|
|
targetDistance: '21.1 км',
|
|
targetDate: '2024-08-15',
|
|
completed: false,
|
|
workouts: [
|
|
{ id: 1, week: 4, day: 1, type: 'easy', distance: 5, completed: true },
|
|
{ id: 2, week: 4, day: 3, type: 'tempo', distance: 8, completed: false },
|
|
{ id: 3, week: 4, day: 5, type: 'long', distance: 12, completed: false }
|
|
]
|
|
}
|
|
return { success: true, data: currentTrainingPlan.value }
|
|
}
|
|
})
|
|
}
|
|
|
|
const fetchWorkoutHistory = async (limit = 10) => {
|
|
return withStoreLoading(async () => {
|
|
try {
|
|
const response = await apiClient.get(`/workouts?limit=${limit}`)
|
|
workoutHistory.value = response.data
|
|
return { success: true, data: workoutHistory.value }
|
|
} catch (error) {
|
|
console.warn('Workouts endpoint not available, using mock data', error)
|
|
workoutHistory.value = [
|
|
{
|
|
id: 1,
|
|
type: 'easy',
|
|
distance_km: 5.2,
|
|
duration_min: 28,
|
|
pace: '5:23',
|
|
calories: 320,
|
|
date: '2024-03-18'
|
|
},
|
|
{
|
|
id: 2,
|
|
type: 'interval',
|
|
distance_km: 8.1,
|
|
duration_min: 42,
|
|
pace: '5:11',
|
|
calories: 510,
|
|
date: '2024-03-16'
|
|
},
|
|
{
|
|
id: 3,
|
|
type: 'long',
|
|
distance_km: 15.5,
|
|
duration_min: 85,
|
|
pace: '5:29',
|
|
calories: 980,
|
|
date: '2024-03-14'
|
|
}
|
|
]
|
|
return { success: true, data: workoutHistory.value }
|
|
}
|
|
})
|
|
}
|
|
|
|
// Специализированные методы для обновления данных
|
|
const addPersonalBest = async (bestData) => {
|
|
return withStoreLoading(async () => {
|
|
try {
|
|
const response = await apiClient.post('/personal-bests', bestData)
|
|
personalBests.value.push(response.data)
|
|
return { success: true, data: response.data }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
})
|
|
}
|
|
|
|
const registerForEvent = async (eventId) => {
|
|
return withStoreLoading(async () => {
|
|
try {
|
|
const response = await apiClient.post('/events/register', { event_id: eventId })
|
|
// Обновляем список событий
|
|
await fetchUpcomingEvents()
|
|
return { success: true, data: response.data }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
})
|
|
}
|
|
|
|
const completeWorkout = async (workoutId) => {
|
|
return withStoreLoading(async () => {
|
|
try {
|
|
const response = await apiClient.patch(`/workouts/${workoutId}/complete`)
|
|
// Обновляем историю тренировок и статистику
|
|
await Promise.all([
|
|
fetchWorkoutHistory(),
|
|
fetchUserStats()
|
|
])
|
|
return { success: true, data: response.data }
|
|
} catch (error) {
|
|
return { success: false, error: error.message }
|
|
}
|
|
})
|
|
}
|
|
|
|
// Пакетная загрузка всех данных пользователя
|
|
const fetchAllUserData = async () => {
|
|
return withStoreLoading(async () => {
|
|
await Promise.all([
|
|
fetchUserStats(),
|
|
fetchUserAchievements(),
|
|
fetchPersonalBests(),
|
|
fetchUpcomingEvents(),
|
|
fetchCurrentTrainingPlan(),
|
|
fetchWorkoutHistory()
|
|
])
|
|
return { success: true }
|
|
})
|
|
}
|
|
|
|
// Сброс store
|
|
const resetUserStore = () => {
|
|
userStats.value = null
|
|
userTraining.value = null
|
|
userAchievements.value = []
|
|
personalBests.value = []
|
|
upcomingEvents.value = []
|
|
currentTrainingPlan.value = null
|
|
workoutHistory.value = []
|
|
loading.value = false
|
|
error.value = ''
|
|
}
|
|
|
|
return {
|
|
// State
|
|
userStats,
|
|
userTraining,
|
|
userAchievements,
|
|
personalBests,
|
|
upcomingEvents,
|
|
currentTrainingPlan,
|
|
workoutHistory,
|
|
loading,
|
|
error,
|
|
|
|
// Getters
|
|
completedAchievements,
|
|
pendingAchievements,
|
|
achievementProgress,
|
|
verifiedPersonalBests,
|
|
confirmedEvents,
|
|
totalWorkouts,
|
|
totalCalories,
|
|
|
|
// Actions
|
|
fetchUserStats,
|
|
fetchUserAchievements,
|
|
fetchPersonalBests,
|
|
fetchUpcomingEvents,
|
|
fetchCurrentTrainingPlan,
|
|
fetchWorkoutHistory,
|
|
fetchAllUserData,
|
|
addPersonalBest,
|
|
registerForEvent,
|
|
completeWorkout,
|
|
resetUserStore
|
|
}
|
|
}) |