mirror of
https://github.com/sstent/FitTrack_ReportGenerator.git
synced 2026-01-29 10:31:51 +00:00
This commit introduces the initial version of the FitTrack Report Generator, a FastAPI application for analyzing workout files. Key features include: - Parsing of FIT, TCX, and GPX workout files. - Analysis of power, heart rate, speed, and elevation data. - Generation of summary reports and charts. - REST API for single and batch workout analysis. The project structure has been set up with a `src` directory for core logic, an `api` directory for the FastAPI application, and a `tests` directory for unit, integration, and contract tests. The development workflow is configured to use Docker and modern Python tooling.
141 lines
4.5 KiB
JavaScript
141 lines
4.5 KiB
JavaScript
class ActivitiesPage {
|
|
constructor() {
|
|
this.currentPage = 1;
|
|
this.pageSize = 25;
|
|
this.totalPages = 1;
|
|
this.activities = [];
|
|
this.filters = {};
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.loadActivities();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
async loadActivities() {
|
|
try {
|
|
const params = new URLSearchParams({
|
|
page: this.currentPage,
|
|
per_page: this.pageSize,
|
|
...this.filters
|
|
});
|
|
|
|
const response = await fetch(`/api/activities?${params}`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to load activities');
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
this.activities = data.activities;
|
|
this.totalPages = Math.ceil(data.total / this.pageSize);
|
|
|
|
this.renderTable();
|
|
this.renderPagination();
|
|
} catch (error) {
|
|
console.error('Failed to load activities:', error);
|
|
this.showError('Failed to load activities');
|
|
}
|
|
}
|
|
|
|
renderTable() {
|
|
const tbody = document.getElementById('activities-tbody');
|
|
if (!tbody) return;
|
|
|
|
if (!this.activities || this.activities.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="6">No activities found</td></tr>';
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = '';
|
|
|
|
this.activities.forEach((activity, index) => {
|
|
const row = this.createTableRow(activity, index);
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
createTableRow(activity, index) {
|
|
const row = document.createElement('tr');
|
|
row.className = index % 2 === 0 ? 'row-even' : 'row-odd';
|
|
|
|
row.innerHTML = `
|
|
<td>${Utils.formatDate(activity.start_time)}</td>
|
|
<td>${activity.activity_type || '-'}</td>
|
|
<td>${Utils.formatDuration(activity.duration)}</td>
|
|
<td>${Utils.formatDistance(activity.distance)}</td>
|
|
<td>${Utils.formatHeartRate(activity.max_heart_rate)}</td>
|
|
<td>${Utils.formatHeartRate(activity.avg_heart_rate)}</td>
|
|
<td>${Utils.formatPower(activity.avg_power)}</td>
|
|
<td>${activity.calories ? activity.calories.toLocaleString() : '-'}</td>
|
|
`;
|
|
|
|
return row;
|
|
}
|
|
|
|
renderPagination() {
|
|
const pagination = document.getElementById('pagination');
|
|
if (!pagination) return;
|
|
|
|
if (this.totalPages <= 1) {
|
|
pagination.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
let paginationHtml = '';
|
|
|
|
// Previous button
|
|
paginationHtml += `
|
|
<li class="${this.currentPage === 1 ? 'disabled' : ''}">
|
|
<a href="#" onclick="activitiesPage.changePage(${this.currentPage - 1}); return false;">Previous</a>
|
|
</li>
|
|
`;
|
|
|
|
// Page numbers
|
|
for (let i = 1; i <= this.totalPages; i++) {
|
|
if (i === 1 || i === this.totalPages || (i >= this.currentPage - 2 && i <= this.currentPage + 2)) {
|
|
paginationHtml += `
|
|
<li class="${i === this.currentPage ? 'active' : ''}">
|
|
<a href="#" onclick="activitiesPage.changePage(${i}); return false;">${i}</a>
|
|
</li>
|
|
`;
|
|
} else if (i === this.currentPage - 3 || i === this.currentPage + 3) {
|
|
paginationHtml += '<li><span>...</span></li>';
|
|
}
|
|
}
|
|
|
|
// Next button
|
|
paginationHtml += `
|
|
<li class="${this.currentPage === this.totalPages ? 'disabled' : ''}">
|
|
<a href="#" onclick="activitiesPage.changePage(${this.currentPage + 1}); return false;">Next</a>
|
|
</li>
|
|
`;
|
|
|
|
pagination.innerHTML = paginationHtml;
|
|
}
|
|
|
|
changePage(page) {
|
|
if (page < 1 || page > this.totalPages) return;
|
|
this.currentPage = page;
|
|
this.loadActivities();
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// We can add filter event listeners here if needed
|
|
}
|
|
|
|
showError(message) {
|
|
const tbody = document.getElementById('activities-tbody');
|
|
if (tbody) {
|
|
tbody.innerHTML = `<tr><td colspan="6">Error: ${message}</td></tr>`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize activities page when DOM is loaded
|
|
let activitiesPage;
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
activitiesPage = new ActivitiesPage();
|
|
});
|