Spaces:
Running
Running
| import './style.css'; | |
| import { parseCSV } from './utils/csvParser'; | |
| import { calculateMetricACWR } from './utils/metricAcwr'; | |
| import type { Activity } from './types'; | |
| import { | |
| createDistanceChart, | |
| createDurationChart, | |
| createTSSChart, | |
| destroyAllCharts, | |
| } from './components/charts'; | |
| // DOM elements | |
| const csvUpload = document.getElementById('csv-upload') as HTMLInputElement; | |
| const ftpInput = document.getElementById('ftp-input') as HTMLInputElement; | |
| const uploadStatus = document.getElementById('upload-status') as HTMLDivElement; | |
| const chartsSection = document.getElementById('charts-section') as HTMLElement; | |
| const filterSection = document.getElementById('filter-section') as HTMLElement; | |
| const runningOnlyFilter = document.getElementById('running-only-filter') as HTMLInputElement; | |
| // Store all activities globally | |
| let allActivities: Activity[] = []; | |
| // Event listeners | |
| csvUpload?.addEventListener('change', handleFileUpload); | |
| runningOnlyFilter?.addEventListener('change', handleFilterChange); | |
| async function handleFileUpload(event: Event): Promise<void> { | |
| const input = event.target as HTMLInputElement; | |
| const file = input.files?.[0]; | |
| if (!file) { | |
| return; | |
| } | |
| uploadStatus.textContent = 'Processing CSV file...'; | |
| uploadStatus.className = ''; | |
| try { | |
| // Get FTP value from input | |
| const ftp = parseInt(ftpInput.value) || 343; | |
| // Parse CSV with user-provided FTP | |
| allActivities = await parseCSV(file, ftp); | |
| if (allActivities.length === 0) { | |
| throw new Error('No valid activities found in the CSV file'); | |
| } | |
| // Reset filter | |
| if (runningOnlyFilter) { | |
| runningOnlyFilter.checked = false; | |
| } | |
| // Render charts with all activities | |
| renderCharts(allActivities); | |
| // Show filter and charts sections | |
| filterSection.classList.remove('hidden'); | |
| chartsSection.classList.remove('hidden'); | |
| // Update status | |
| uploadStatus.textContent = `Successfully loaded ${allActivities.length} activities`; | |
| uploadStatus.className = 'success'; | |
| } catch (error) { | |
| console.error('Error processing file:', error); | |
| uploadStatus.textContent = error instanceof Error ? error.message : 'Failed to process CSV file'; | |
| uploadStatus.className = 'error'; | |
| filterSection.classList.add('hidden'); | |
| chartsSection.classList.add('hidden'); | |
| } | |
| } | |
| function handleFilterChange(): void { | |
| if (allActivities.length === 0) return; | |
| const filteredActivities = runningOnlyFilter.checked | |
| ? allActivities.filter(activity => activity.activityType === 'Running') | |
| : allActivities; | |
| renderCharts(filteredActivities); | |
| } | |
| function renderCharts(activities: Activity[]): void { | |
| // Calculate date range from all activities for consistency | |
| let dateRange: { start: Date; end: Date } | undefined; | |
| if (allActivities.length > 0) { | |
| const sortedAll = [...allActivities].sort((a, b) => a.date.getTime() - b.date.getTime()); | |
| // Normalize to midnight to avoid timezone/time comparison issues | |
| const startDate = new Date(sortedAll[0].date); | |
| startDate.setHours(0, 0, 0, 0); | |
| const endDate = new Date(sortedAll[sortedAll.length - 1].date); | |
| endDate.setHours(23, 59, 59, 999); | |
| dateRange = { | |
| start: startDate, | |
| end: endDate, | |
| }; | |
| } | |
| // Calculate ACWR for each metric with consistent date range | |
| const distanceData = calculateMetricACWR( | |
| activities, | |
| (activity) => activity.distance, | |
| dateRange | |
| ); | |
| const durationData = calculateMetricACWR( | |
| activities, | |
| (activity) => activity.duration, | |
| dateRange | |
| ); | |
| const tssData = calculateMetricACWR( | |
| activities, | |
| (activity) => activity.trainingStressScore, | |
| dateRange | |
| ); | |
| // Destroy existing charts | |
| destroyAllCharts(); | |
| // Create new charts | |
| createDistanceChart(distanceData); | |
| createDurationChart(durationData); | |
| createTSSChart(tssData); | |
| } | |
| // Initialize | |
| console.log('Training Load Data Visualization initialized'); | |