diff --git a/src/components/UserDashboard.vue b/src/components/UserDashboard.vue index e20a3db..c15d6fb 100644 --- a/src/components/UserDashboard.vue +++ b/src/components/UserDashboard.vue @@ -130,6 +130,30 @@ Total gain + + @@ -328,6 +352,86 @@ const currentDate = computed(() => { }) }) +// Helper to get start of week (Monday) +function getWeekStart(date) { + const d = new Date(date) + const day = d.getDay() + const diff = d.getDate() - day + (day === 0 ? -6 : 1) // Adjust for Monday start + d.setDate(diff) + d.setHours(0, 0, 0, 0) + return d +} + +// Helper to get end of week (Sunday) +function getWeekEnd(date) { + const start = getWeekStart(date) + const end = new Date(start) + end.setDate(end.getDate() + 6) + end.setHours(23, 59, 59, 999) + return end +} + +// Calculate TSS for a date range +function calculateTSSForRange(workouts, startDate, endDate) { + return workouts + .filter(w => { + const workoutDate = new Date(w.scheduled_date) + return workoutDate >= startDate && + workoutDate <= endDate && + w.status === 'completed' && + w.tss != null + }) + .reduce((sum, w) => sum + (w.tss || 0), 0) +} + +// Current week TSS +const currentWeekTSS = computed(() => { + const now = new Date() + const weekStart = getWeekStart(now) + const weekEnd = getWeekEnd(now) + const tss = calculateTSSForRange(recentWorkouts.value, weekStart, weekEnd) + return Math.round(tss) +}) + +// Previous week TSS +const previousWeekTSS = computed(() => { + const now = new Date() + const currentWeekStart = getWeekStart(now) + const prevWeekEnd = new Date(currentWeekStart) + prevWeekEnd.setDate(prevWeekEnd.getDate() - 1) + prevWeekEnd.setHours(23, 59, 59, 999) + const prevWeekStart = getWeekStart(prevWeekEnd) + const tss = calculateTSSForRange(recentWorkouts.value, prevWeekStart, prevWeekEnd) + return Math.round(tss) +}) + +// TSS percentage change from previous week +const tssChangePercent = computed(() => { + if (previousWeekTSS.value === 0) { + return currentWeekTSS.value > 0 ? 100 : 0 + } + return Math.round(((currentWeekTSS.value - previousWeekTSS.value) / previousWeekTSS.value) * 100) +}) + +// TSS change display text +const tssChangeText = computed(() => { + if (previousWeekTSS.value === 0 && currentWeekTSS.value === 0) { + return 'No data' + } + if (previousWeekTSS.value === 0) { + return '+100% vs last week' + } + const sign = tssChangePercent.value >= 0 ? '+' : '' + return `${sign}${tssChangePercent.value}% vs last week` +}) + +// TSS change CSS class +const tssChangeClass = computed(() => { + if (tssChangePercent.value > 0) return 'positive' + if (tssChangePercent.value < 0) return 'negative' + return 'neutral' +}) + onMounted(async () => { try { await loadRecentWorkouts() @@ -567,6 +671,23 @@ body.sidebar-collapsed .dashboard-container { background: linear-gradient(135deg, #e63946, #d62828); } +.icon-badge-tss { + background: linear-gradient(135deg, #8b5cf6, #6366f1); +} + +.metric-card-featured { + border: 2px solid var(--color-primary); + background: linear-gradient(135deg, var(--color-surface), rgba(102, 126, 234, 0.05)); +} + +.metric-change.negative { + color: #ef4444; +} + +.metric-change.neutral { + color: var(--color-text-secondary); +} + /* Workouts List */ .workouts-list-modern { display: flex; diff --git a/src/components/WorkoutCalendar.vue b/src/components/WorkoutCalendar.vue index d25b689..1b84c69 100644 --- a/src/components/WorkoutCalendar.vue +++ b/src/components/WorkoutCalendar.vue @@ -3,14 +3,26 @@
- -
-
-

Workout Calendar

-

Plan and track your training schedule

+ +
+
+ +
+

{{ weekDateRange }}

+ +
+
-
- -
- -

{{ monthYear }}

- + +
+ +
+
+ +
+ {{ day.dayName }} + {{ day.dayNumber }} +
+ + +
+
+
+ {{ getWorkoutIcon(workout.type) }} +
+
+
{{ workout.title }}
+
+ {{ formatWorkoutDuration(workout.duration) }} + {{ workout.tss }} TSS +
+
+ {{ truncateText(workout.description, 80) }} +
+
+
+ +
+
+ + +
+ Rest day +
+
+
+
+ + +
@@ -202,147 +306,14 @@
- -
-
-
-
Sunday
-
Monday
-
Tuesday
-
Wednesday
-
Thursday
-
Friday
-
Saturday
-
- -
-
-
- {{ day.day }} - Today -
-
-
-
- {{ getWorkoutIcon(workout.type) }} - {{ workout.title }} -
- -
-
-
-
-
-
- - - - -
-
-
-

Upcoming Workouts

-

Your next 5 scheduled workouts

-
-
- -
- - - - - - - - - - - - - - - - - - - - - -
DateWorkoutTypeDurationStatus
-
- {{ formatDateDay(workout.scheduled_date) }} - {{ formatDateMonth(workout.scheduled_date) }} -
-
-
- {{ getWorkoutIcon(workout.type) }} - {{ workout.title }} -
-
- {{ workout.type }} - - - - {{ workout.duration }} min - - - {{ workout.status }} - - - -
-
- -
-
📅
-

No Upcoming Workouts

-

- You don't have any workouts scheduled yet. Click the "Add Workout" button to plan your first training session! -

- -
-
@@ -375,46 +346,216 @@ const newWorkout = ref({ file_type: null, }) -const monthYear = computed(() => { - return currentDate.value.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }) +// Week date range display (e.g., "Jan 20 - Jan 26, 2025") +const weekDateRange = computed(() => { + const weekStart = getWeekStart(currentDate.value) + const weekEnd = getWeekEnd(currentDate.value) + const startMonth = weekStart.toLocaleDateString('en-US', { month: 'short' }) + const endMonth = weekEnd.toLocaleDateString('en-US', { month: 'short' }) + const year = weekEnd.getFullYear() + + if (startMonth === endMonth) { + return `${startMonth} ${weekStart.getDate()} - ${weekEnd.getDate()}, ${year}` + } + return `${startMonth} ${weekStart.getDate()} - ${endMonth} ${weekEnd.getDate()}, ${year}` }) -const calendarDays = computed(() => { - const year = currentDate.value.getFullYear() - const month = currentDate.value.getMonth() - - const firstDay = new Date(year, month, 1) - const lastDay = new Date(year, month + 1, 0) - const startDate = new Date(firstDay) - startDate.setDate(startDate.getDate() - firstDay.getDay()) +// Check if viewing current week +const isCurrentWeek = computed(() => { + const now = new Date() + const currentWeekStart = getWeekStart(now) + const viewedWeekStart = getWeekStart(currentDate.value) + return currentWeekStart.getTime() === viewedWeekStart.getTime() +}) +// Days of the viewed week (Monday to Sunday) +const weekDays = computed(() => { + const weekStart = getWeekStart(currentDate.value) const days = [] - let currentDay = new Date(startDate) + const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] - while (currentDay <= lastDay || currentDay.getDay() !== 0) { - const dateStr = currentDay.toISOString().split('T')[0] - const dayWorkouts = workouts.value.filter(w => + for (let i = 0; i < 7; i++) { + const day = new Date(weekStart) + day.setDate(day.getDate() + i) + const dateStr = day.toISOString().split('T')[0] + const dayWorkouts = workouts.value.filter(w => w.scheduled_date.split('T')[0] === dateStr ) days.push({ date: dateStr, - day: currentDay.getDate(), - isCurrentMonth: currentDay.getMonth() === month, - isToday: isToday(currentDay), + dayName: dayNames[i], + dayNumber: day.getDate(), + isToday: isToday(day), workouts: dayWorkouts, }) - - currentDay.setDate(currentDay.getDate() + 1) } return days }) -const upcomingWorkouts = computed(() => { - return workouts.value - .filter(w => new Date(w.scheduled_date) >= new Date()) - .sort((a, b) => new Date(a.scheduled_date) - new Date(b.scheduled_date)) +// Viewed week stats (for the sidebar) +const viewedWeekTSS = computed(() => { + const weekStart = getWeekStart(currentDate.value) + const weekEnd = getWeekEnd(currentDate.value) + return Math.round(calculateTSSForRange(workouts.value, weekStart, weekEnd)) +}) + +const viewedWeekDuration = computed(() => { + const weekStart = getWeekStart(currentDate.value) + const weekEnd = getWeekEnd(currentDate.value) + const totalMinutes = calculateDurationForRange(workouts.value, weekStart, weekEnd) + const hours = Math.floor(totalMinutes / 60) + const minutes = totalMinutes % 60 + if (hours > 0) { + return `${hours}h ${minutes}m` + } + return `${minutes}m` +}) + +const viewedWeekWorkoutCount = computed(() => { + const weekStart = getWeekStart(currentDate.value) + const weekEnd = getWeekEnd(currentDate.value) + return workouts.value.filter(w => { + const workoutDate = new Date(w.scheduled_date) + return workoutDate >= weekStart && workoutDate <= weekEnd + }).length +}) + +const viewedWeekCompletedCount = computed(() => { + const weekStart = getWeekStart(currentDate.value) + const weekEnd = getWeekEnd(currentDate.value) + return workouts.value.filter(w => { + const workoutDate = new Date(w.scheduled_date) + return workoutDate >= weekStart && workoutDate <= weekEnd && w.status === 'completed' + }).length +}) + +const viewedWeekScheduledCount = computed(() => { + const weekStart = getWeekStart(currentDate.value) + const weekEnd = getWeekEnd(currentDate.value) + return workouts.value.filter(w => { + const workoutDate = new Date(w.scheduled_date) + return workoutDate >= weekStart && workoutDate <= weekEnd && w.status === 'scheduled' + }).length +}) + +const viewedWeekSkippedCount = computed(() => { + const weekStart = getWeekStart(currentDate.value) + const weekEnd = getWeekEnd(currentDate.value) + return workouts.value.filter(w => { + const workoutDate = new Date(w.scheduled_date) + return workoutDate >= weekStart && workoutDate <= weekEnd && w.status === 'skipped' + }).length +}) + +// Weekly TSS calculations +function getWeekStart(date) { + const d = new Date(date) + const day = d.getDay() + // Adjust to Monday as start of week (day 0 = Sunday, so Monday = 1) + const diff = d.getDate() - day + (day === 0 ? -6 : 1) + d.setDate(diff) + d.setHours(0, 0, 0, 0) + return d +} + +function getWeekEnd(date) { + const weekStart = getWeekStart(date) + const weekEnd = new Date(weekStart) + weekEnd.setDate(weekEnd.getDate() + 6) + weekEnd.setHours(23, 59, 59, 999) + return weekEnd +} + +function calculateTSSForRange(workoutList, startDate, endDate) { + return workoutList + .filter(w => { + const workoutDate = new Date(w.scheduled_date) + return workoutDate >= startDate && + workoutDate <= endDate && + w.status === 'completed' && + w.tss != null + }) + .reduce((sum, w) => sum + (w.tss || 0), 0) +} + +function calculateDurationForRange(workoutList, startDate, endDate) { + return workoutList + .filter(w => { + const workoutDate = new Date(w.scheduled_date) + return workoutDate >= startDate && + workoutDate <= endDate && + w.status === 'completed' + }) + .reduce((sum, w) => sum + (w.duration || 0), 0) +} + +function countWorkoutsForRange(workoutList, startDate, endDate) { + return workoutList.filter(w => { + const workoutDate = new Date(w.scheduled_date) + return workoutDate >= startDate && + workoutDate <= endDate && + w.status === 'completed' + }).length +} + +const currentWeekTSS = computed(() => { + const now = new Date() + const weekStart = getWeekStart(now) + const weekEnd = getWeekEnd(now) + return Math.round(calculateTSSForRange(workouts.value, weekStart, weekEnd)) +}) + +const previousWeekTSS = computed(() => { + const now = new Date() + const currentWeekStart = getWeekStart(now) + const prevWeekEnd = new Date(currentWeekStart) + prevWeekEnd.setDate(prevWeekEnd.getDate() - 1) + prevWeekEnd.setHours(23, 59, 59, 999) + const prevWeekStart = getWeekStart(prevWeekEnd) + return Math.round(calculateTSSForRange(workouts.value, prevWeekStart, prevWeekEnd)) +}) + +const tssChangePercent = computed(() => { + if (previousWeekTSS.value === 0) { + return currentWeekTSS.value > 0 ? 100 : 0 + } + return Math.round(((currentWeekTSS.value - previousWeekTSS.value) / previousWeekTSS.value) * 100) +}) + +const tssChangeText = computed(() => { + const percent = tssChangePercent.value + if (percent === 0) return 'No change' + const sign = percent > 0 ? '+' : '' + return `${sign}${percent}% vs last week` +}) + +const tssChangeClass = computed(() => { + const percent = tssChangePercent.value + if (percent > 0) return 'tss-change-up' + if (percent < 0) return 'tss-change-down' + return 'tss-change-neutral' +}) + +const currentWeekDuration = computed(() => { + const now = new Date() + const weekStart = getWeekStart(now) + const weekEnd = getWeekEnd(now) + const totalMinutes = calculateDurationForRange(workouts.value, weekStart, weekEnd) + const hours = Math.floor(totalMinutes / 60) + const minutes = totalMinutes % 60 + if (hours > 0) { + return `${hours}h ${minutes}m` + } + return `${minutes}m` +}) + +const currentWeekWorkoutCount = computed(() => { + const now = new Date() + const weekStart = getWeekStart(now) + const weekEnd = getWeekEnd(now) + return countWorkoutsForRange(workouts.value, weekStart, weekEnd) }) onMounted(async () => { @@ -577,12 +718,21 @@ function resetForm() { } } -function previousMonth() { - currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1) +// Week navigation +function previousWeek() { + const newDate = new Date(currentDate.value) + newDate.setDate(newDate.getDate() - 7) + currentDate.value = newDate } -function nextMonth() { - currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() + 1) +function nextWeek() { + const newDate = new Date(currentDate.value) + newDate.setDate(newDate.getDate() + 7) + currentDate.value = newDate +} + +function goToToday() { + currentDate.value = new Date() } function selectWorkout(workout) { @@ -605,6 +755,22 @@ function formatDuration(seconds) { return `${minutes}m` } +function formatWorkoutDuration(minutes) { + if (!minutes) return '—' + const hours = Math.floor(minutes / 60) + const mins = minutes % 60 + if (hours > 0) { + return `${hours}:${mins.toString().padStart(2, '0')}` + } + return `${mins}m` +} + +function truncateText(text, maxLength) { + if (!text) return '' + if (text.length <= maxLength) return text + return text.substring(0, maxLength) + '...' +} + function getWorkoutColor(type) { const typeObj = workoutTypes.value.find(t => t.name === type) return typeObj?.color || '#667eea' @@ -614,18 +780,10 @@ function getWorkoutIcon(type) { const typeObj = workoutTypes.value.find(t => t.name === type) return typeObj?.icon || '🏋️' } - -function formatDateDay(dateStr) { - return new Date(dateStr).toLocaleDateString('en-US', { day: 'numeric' }) -} - -function formatDateMonth(dateStr) { - return new Date(dateStr).toLocaleDateString('en-US', { month: 'short' }) -}