111 lines
4.1 KiB
JavaScript
111 lines
4.1 KiB
JavaScript
/**
|
|
* Main JavaScript file for Fitness Parser
|
|
* Uses jQuery for DOM manipulation
|
|
*/
|
|
|
|
$(document).ready(function() {
|
|
console.log('Fitness Parser JS loaded');
|
|
|
|
// Simple example functionality
|
|
$('.card').on('click', function() {
|
|
$(this).toggleClass('bg-light');
|
|
});
|
|
|
|
// Bootstrap tooltips (explanations of the metrics)
|
|
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(
|
|
(element) => new bootstrap.Tooltip(element)
|
|
);
|
|
|
|
// Exercise search: open the history right after a suggestion is picked from the list.
|
|
// Typing, pasting and deleting have their own inputType, so a half-typed name that
|
|
// equals a shorter exercise name doesn't submit early; Enter/button always work.
|
|
const $exerciseInput = $('#exercise-input');
|
|
if ($exerciseInput.length) {
|
|
const exerciseNames = new Set(
|
|
$('#exercise-names option').map((_, option) => option.value).get()
|
|
);
|
|
$exerciseInput.on('input', function(event) {
|
|
const inputType = event.originalEvent && event.originalEvent.inputType;
|
|
const picked = !inputType || inputType === 'insertReplacementText';
|
|
if (picked && exerciseNames.has(this.value)) {
|
|
this.form.submit();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Exercise progress chart (Chart.js): two series over time, each on its own y axis.
|
|
// Points are [UTC timestamp in ms, value]; the x axis is linear so gaps between
|
|
// trainings are proportional to the real time.
|
|
const chartDataElement = document.getElementById('progress-chart-data');
|
|
if (chartDataElement && window.Chart) {
|
|
const series = JSON.parse(chartDataElement.textContent).series;
|
|
const colors = ['#0d6efd', '#fd7e14'];
|
|
const formatDate = (timestamp, options) =>
|
|
new Date(timestamp).toLocaleDateString('ru-RU', { ...options, timeZone: 'UTC' });
|
|
|
|
const scales = {
|
|
x: {
|
|
type: 'linear',
|
|
ticks: {
|
|
maxTicksLimit: 8,
|
|
callback: (value) => formatDate(value, { month: 'short', year: 'numeric' }),
|
|
},
|
|
},
|
|
};
|
|
series.forEach((item, index) => {
|
|
scales['y' + index] = {
|
|
position: index === 0 ? 'left' : 'right',
|
|
grid: { drawOnChartArea: index === 0 },
|
|
title: { display: true, text: item.label, color: colors[index] },
|
|
};
|
|
});
|
|
|
|
new Chart(document.getElementById('progress-chart'), {
|
|
type: 'line',
|
|
data: {
|
|
datasets: series.map((item, index) => ({
|
|
label: item.label,
|
|
data: item.points.map(([x, y]) => ({ x, y })),
|
|
yAxisID: 'y' + index,
|
|
borderColor: colors[index],
|
|
backgroundColor: colors[index],
|
|
borderWidth: 2,
|
|
pointRadius: 3,
|
|
tension: 0.2,
|
|
})),
|
|
},
|
|
options: {
|
|
maintainAspectRatio: false,
|
|
interaction: { mode: 'nearest', axis: 'x', intersect: false },
|
|
scales,
|
|
plugins: {
|
|
tooltip: {
|
|
callbacks: {
|
|
title: (items) => formatDate(items[0].parsed.x, {}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
// Tables with search, sorting and paging by DataTables
|
|
const tableOptions = {
|
|
pageLength: 50,
|
|
lengthMenu: [25, 50, 100, 200],
|
|
order: [], // keep the order from the server: newest training first
|
|
language: { url: document.body.dataset.datatablesLang },
|
|
};
|
|
|
|
// All trainings: date, trainer, exercise, approaches
|
|
$('#trainings-table').DataTable({
|
|
...tableOptions,
|
|
columnDefs: [{ targets: 3, orderable: false }], // approaches
|
|
});
|
|
|
|
// History of one exercise: date, trainer, approaches, max weight
|
|
$('#exercise-history-table').DataTable({
|
|
...tableOptions,
|
|
columnDefs: [{ targets: 2, orderable: false }], // approaches
|
|
});
|
|
});
|