// 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 } })