glutamatt's picture
glutamatt HF Staff
init
35527e2 verified
raw
history blame
4.17 kB
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');