File size: 4,167 Bytes
35527e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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');