Update repo
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers styles for frontend and enqueues them when shortcodes are used.
|
||||
*/
|
||||
function mystat_enqueue_frontend_assets() {
|
||||
// Register the stylesheet. It will be enqueued by the shortcodes when needed.
|
||||
$plugin_version = '1.0';
|
||||
wp_register_style(
|
||||
'mystat-frontend-styles',
|
||||
MYSTAT_PLUGIN_URL . 'assets/css/frontend.css',
|
||||
array(),
|
||||
$plugin_version
|
||||
);
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers styles for frontend and enqueues them when shortcodes are used.
|
||||
*/
|
||||
function statpress_enqueue_frontend_assets() {
|
||||
// Register the stylesheet. It will be enqueued by the shortcodes when needed.
|
||||
$plugin_version = '1.0';
|
||||
wp_register_style(
|
||||
'statpress-frontend-styles',
|
||||
STATPRESS_PLUGIN_URL . 'assets/css/frontend.css',
|
||||
array(),
|
||||
$plugin_version
|
||||
);
|
||||
}
|
||||
+392
-392
@@ -1,393 +1,393 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejestruje shortcode [moje_statystyki].
|
||||
*/
|
||||
function mystat_register_shortcode() {
|
||||
add_shortcode( 'moje_statystyki', 'mystat_shortcode_handler' );
|
||||
add_shortcode( 'moje_statystyki_wpis', 'mystat_single_activity_shortcode_handler' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Funkcja obsługująca shortcode.
|
||||
* @param array $atts Atrybuty shortcode'u (np. year, month).
|
||||
* @return string HTML do wyświetlenia.
|
||||
*/
|
||||
function mystat_shortcode_handler( $atts ) {
|
||||
wp_enqueue_style( 'mystat-frontend-styles' );
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// Ustawienie domyślnych atrybutów (bieżący rok i miesiąc)
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'year' => current_time( 'Y' ),
|
||||
'month' => current_time( 'n' ),
|
||||
),
|
||||
$atts,
|
||||
'moje_statystyki'
|
||||
);
|
||||
|
||||
$year = intval( $atts['year'] );
|
||||
$month = intval( $atts['month'] );
|
||||
|
||||
// Pobieranie danych z bazy
|
||||
$table_activities = $wpdb->prefix . 'mystat_activities';
|
||||
$sql = $wpdb->prepare(
|
||||
"
|
||||
SELECT a.*, c.name as category_name, eq.name as equipment_name
|
||||
FROM $table_activities a
|
||||
LEFT JOIN {$wpdb->prefix}mystat_categories c ON a.category_id = c.id
|
||||
LEFT JOIN {$wpdb->prefix}mystat_equipment eq ON a.equipment_id = eq.id
|
||||
WHERE YEAR(a.date) = %d AND MONTH(a.date) = %d
|
||||
ORDER BY a.date ASC
|
||||
",
|
||||
$year,
|
||||
$month
|
||||
);
|
||||
|
||||
$activities = $wpdb->get_results( $sql );
|
||||
|
||||
// Obliczanie podsumowań
|
||||
$total_distance = 0;
|
||||
$total_seconds = 0;
|
||||
foreach ( $activities as $activity ) {
|
||||
$total_distance += $activity->distance;
|
||||
if ( ! empty( $activity->duration ) ) {
|
||||
list($h, $m, $s) = explode( ':', $activity->duration );
|
||||
$total_seconds += $h * 3600 + $m * 60 + $s;
|
||||
}
|
||||
}
|
||||
$hours = floor( $total_seconds / 3600 );
|
||||
$minutes = floor( ( $total_seconds % 3600 ) / 60 );
|
||||
$total_duration_formatted = sprintf( '%d godz. %d min.', $hours, $minutes );
|
||||
|
||||
// Rozpoczęcie buforowania wyjścia
|
||||
ob_start();
|
||||
?>
|
||||
<div class="mystats-shortcode-container">
|
||||
|
||||
<h3>Podsumowanie miesiąca</h3>
|
||||
<table class="mystats-summary-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Całkowity dystans</th>
|
||||
<th>Całkowity czas</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><?php echo number_format( $total_distance, 2, ',', ' ' ); ?> km</td>
|
||||
<td><?php echo esc_html( $total_duration_formatted ); ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Lista aktywności</h3>
|
||||
<table class="mystats-activity-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
<th>Tytuł</th>
|
||||
<th>Kategoria</th>
|
||||
|
||||
<th>Dystans</th>
|
||||
<th>Czas</th>
|
||||
<th>Sprzęt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ( ! empty( $activities ) ) : ?>
|
||||
<?php foreach ( $activities as $row ) : ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html( date_i18n( 'd.m.Y', strtotime( $row->date ) ) ); ?></td>
|
||||
<td><?php echo esc_html( $row->title ); ?></td>
|
||||
<td><?php echo esc_html( $row->category_name ); ?></td>
|
||||
<td><?php echo number_format( $row->distance, 2, ',', ' ' ); ?> km</td>
|
||||
<td><?php echo esc_html( $row->duration ); ?></td>
|
||||
<td><?php echo esc_html( $row->equipment_name ); ?></td>
|
||||
</tr>
|
||||
<?php if ( $row->avg_speed || $row->avg_heart_rate || $row->avg_cadence ) : ?>
|
||||
<tr class="mystats-activity-details-row">
|
||||
<td colspan="6" class="mystats-activity-details-cell">
|
||||
<table class="mystats-nested-details-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if ( $row->avg_speed ) { echo '<th>Śr. prędkość</th>'; } ?>
|
||||
<?php if ( $row->avg_heart_rate ) { echo '<th>Śr. tętno</th>'; } ?>
|
||||
<?php if ( $row->avg_cadence ) { echo '<th>Śr. kadencja</th>'; } ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<?php if ( $row->avg_speed ) { echo '<td>' . number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h</td>'; } ?>
|
||||
<?php if ( $row->avg_heart_rate ) { echo '<td>' . $row->avg_heart_rate . ' bpm</td>'; } ?>
|
||||
<?php if ( $row->avg_cadence ) { echo '<td>' . $row->avg_cadence . ' rpm</td>'; } ?>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<tr>
|
||||
<td colspan="6">Brak aktywności w tym miesiącu.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
// Zwrócenie zawartości bufora
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function mystat_single_activity_shortcode_handler( $atts ) {
|
||||
wp_enqueue_style( 'mystat-frontend-styles' );
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'id' => 0,
|
||||
),
|
||||
$atts,
|
||||
'moje_statystyki_wpis'
|
||||
);
|
||||
|
||||
$activity_id = intval( $atts['id'] );
|
||||
|
||||
if ( 0 === $activity_id ) {
|
||||
return '<p><strong>Błąd:</strong> Nie podano ID wpisu w shortcode.</p>';
|
||||
}
|
||||
|
||||
// Pobieranie danych z bazy
|
||||
$table_activities = $wpdb->prefix . 'mystat_activities';
|
||||
$sql = $wpdb->prepare(
|
||||
"
|
||||
SELECT a.*, c.name as category_name, c.color as category_color, et.name as event_type_name, eq.name as equipment_name
|
||||
FROM $table_activities a
|
||||
LEFT JOIN {$wpdb->prefix}mystat_categories c ON a.category_id = c.id
|
||||
LEFT JOIN {$wpdb->prefix}mystat_event_types et ON a.event_type_id = et.id
|
||||
LEFT JOIN {$wpdb->prefix}mystat_equipment eq ON a.equipment_id = eq.id
|
||||
WHERE a.id = %d
|
||||
",
|
||||
$activity_id
|
||||
);
|
||||
|
||||
$activity = $wpdb->get_row( $sql );
|
||||
|
||||
if ( ! $activity ) {
|
||||
return '<p><strong>Błąd:</strong> Nie znaleziono wpisu o ID ' . esc_html( $activity_id ) . '.</p>';
|
||||
}
|
||||
|
||||
// Funkcja pomocnicza do wyświetlania wiersza
|
||||
$render_row = function( $label, $value, $unit = '' ) {
|
||||
if ( ! is_null( $value ) && '' !== $value && 0 != $value ) {
|
||||
echo '<tr>';
|
||||
echo '<th>' . esc_html( $label ) . '</th>';
|
||||
echo '<td>' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . '</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
};
|
||||
|
||||
ob_start();
|
||||
|
||||
// Prepare map and chart data if GPX exists
|
||||
$gpx_data = array();
|
||||
$has_gpx_data = false;
|
||||
if ( ! empty( $activity->gpx_url ) ) {
|
||||
$gpx_data = mystat_parse_gpx_data( $activity->gpx_url );
|
||||
$has_gpx_data = ! empty( $gpx_data['points'] );
|
||||
}
|
||||
|
||||
$unique_id = 'mystat-activity-' . esc_attr( $activity->id );
|
||||
|
||||
if ( $has_gpx_data ) {
|
||||
wp_enqueue_style( 'leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' );
|
||||
wp_enqueue_script( 'leaflet-js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', array(), '1.9.4', true );
|
||||
wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
|
||||
|
||||
$map_id = 'map-' . $unique_id;
|
||||
$chart_id = 'chart-' . $unique_id;
|
||||
|
||||
$available_profiles = array();
|
||||
if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) {
|
||||
$available_profiles['elevation'] = 'Wysokość';}
|
||||
if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) {
|
||||
$available_profiles['speed'] = 'Prędkość';}
|
||||
if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) {
|
||||
$available_profiles['hr'] = 'Tętno';}
|
||||
if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) {
|
||||
$available_profiles['cadence'] = 'Kadencja';}
|
||||
|
||||
$has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) );
|
||||
|
||||
wp_register_script( 'mystat-shortcode-loader-' . $activity->id, false );
|
||||
wp_enqueue_script( 'mystat-shortcode-loader-' . $activity->id );
|
||||
|
||||
$js_script = '
|
||||
(function() {
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const uniqueId = "' . esc_js( $unique_id ) . '";
|
||||
const containerEl = document.getElementById(uniqueId);
|
||||
if (!containerEl || typeof L === "undefined" || typeof Chart === "undefined") return;
|
||||
|
||||
const trackPoints = ' . json_encode( $gpx_data['points'] ) . ';
|
||||
const profiles = ' . json_encode( $gpx_data['profiles'] ) . ';
|
||||
let activeChart = null;
|
||||
|
||||
const mapId = "' . esc_js( $map_id ) . '";
|
||||
const mapEl = document.getElementById(mapId);
|
||||
if (mapEl && trackPoints.length > 0) {
|
||||
if (mapEl._leaflet_id) mapEl._leaflet_id = null;
|
||||
const map = L.map(mapId);
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>\' }).addTo(map);
|
||||
const polyline = L.polyline(trackPoints, {color: "' . esc_js( $activity->category_color ) . '"}).addTo(map);
|
||||
map.fitBounds(polyline.getBounds().pad(0.1));
|
||||
}
|
||||
|
||||
const chartId = "' . esc_js( $chart_id ) . '";
|
||||
const chartEl = document.getElementById(chartId);
|
||||
if (!chartEl) return;
|
||||
|
||||
const chartConfigs = {
|
||||
elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" },
|
||||
speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" },
|
||||
hr: { label: "Tętno", unit: "bpm", color: "#c0392b" },
|
||||
cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" }
|
||||
};
|
||||
const xAxisConfigs = {
|
||||
distance: { label: "Dystans (km)", data: profiles.distance },
|
||||
time: { label: "Czas", data: profiles.time, formatter: (s) => s === null ? "" : new Date(s * 1000).toISOString().substr(11, 8) }
|
||||
};
|
||||
|
||||
function renderChart() {
|
||||
if (activeChart) activeChart.destroy();
|
||||
const activeTab = containerEl.querySelector(".mystat-chart-tab.active");
|
||||
if (!activeTab) return;
|
||||
const chartType = activeTab.dataset.type;
|
||||
const xAxisRadio = containerEl.querySelector(\'input[name="xaxis-\' + uniqueId + \'"]:checked\');
|
||||
if (!xAxisRadio) return;
|
||||
const xAxisType = xAxisRadio.value;
|
||||
|
||||
const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data;
|
||||
const filteredY = [], filteredX = [];
|
||||
if(yData) {
|
||||
for(let i=0; i<yData.length; i++) {
|
||||
if (yData[i] !== null) {
|
||||
filteredY.push(yData[i]);
|
||||
filteredX.push(xData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ctx = chartEl.getContext("2d");
|
||||
activeChart = new Chart(ctx, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: filteredX,
|
||||
datasets: [{
|
||||
label: chartConfigs[chartType].label, data: filteredY,
|
||||
borderColor: chartConfigs[chartType].color, backgroundColor: chartConfigs[chartType].color + "20",
|
||||
fill: true, pointRadius: 0, tension: 0.1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: { title: { display: true, text: xAxisConfigs[xAxisType].label }, ticks: { callback: xAxisType === "time" ? xAxisConfigs.time.formatter : (v) => v, maxRotation: 0, autoSkip: true, maxTicksLimit: 7 } },
|
||||
y: { title: { display: true, text: chartConfigs[chartType].unit } }
|
||||
},
|
||||
plugins: { legend: { display: false } },
|
||||
interaction: { intersect: false, mode: "index" },
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
containerEl.querySelectorAll(".mystat-chart-tab").forEach(t => t.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
containerEl.querySelector(".mystat-chart-tab.active").classList.remove("active");
|
||||
e.currentTarget.classList.add("active");
|
||||
renderChart();
|
||||
}));
|
||||
containerEl.querySelectorAll(\'input[name="xaxis-\' + uniqueId + \'"]\').forEach(r => r.addEventListener("change", renderChart));
|
||||
if (containerEl.querySelector(".mystat-chart-tab")) renderChart();
|
||||
});
|
||||
})();';
|
||||
wp_add_inline_script( 'mystat-shortcode-loader-' . $activity->id, $js_script );
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="mystat-single-activity-shortcode" id="<?php echo esc_attr( $unique_id ); ?>">
|
||||
<h4><?php echo esc_html( $activity->title ); ?></h4>
|
||||
<p><em><?php echo esc_html( date_i18n( 'j F Y', strtotime( $activity->date ) ) ); ?></em></p>
|
||||
|
||||
<div class="mystat-single-columns-container">
|
||||
<div class="mystat-single-col">
|
||||
<table class="mystat-single-summary-table">
|
||||
<tbody>
|
||||
<?php $render_row( 'Dystans', number_format( $activity->distance, 2, ',', ' ' ), 'km' ); ?>
|
||||
<?php $render_row( 'Czas trwania', $activity->duration ); ?>
|
||||
<?php $render_row( 'Średnia prędkość', number_format( $activity->avg_speed, 1, ',', ' ' ), 'km/h' ); ?>
|
||||
<?php $render_row( 'Suma wzniosów', $activity->total_elevation_gain, 'm' ); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mystat-single-col">
|
||||
<table class="mystat-single-summary-table">
|
||||
<tbody>
|
||||
<?php $render_row( 'Kategoria', $activity->category_name ); ?>
|
||||
<?php $render_row( 'Sprzęt', $activity->equipment_name ); ?>
|
||||
<?php if ( ! empty( $activity->strava_url ) ) : ?>
|
||||
<tr><th>Strava</th><td><a href="<?php echo esc_url( $activity->strava_url ); ?>" target="_blank" rel="noopener noreferrer">Zobacz aktywność</a></td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ( $has_gpx_data ) : ?>
|
||||
<div id="<?php echo esc_attr( $map_id ); ?>" class="mystat-single-map" style="height: 350px; width: 100%; margin-top: 15px; border-radius: 5px; margin-bottom: 20px;"></div>
|
||||
|
||||
<?php if ( ! empty( $available_profiles ) ) : ?>
|
||||
<div class="mystat-charts-container">
|
||||
<div class="mystat-chart-controls" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; margin-bottom: 15px; border-bottom: 1px solid #eee; padding-bottom: 10px; gap: 10px;">
|
||||
<div class="mystat-chart-tabs" style="display: flex; flex-wrap: wrap; gap: 5px;">
|
||||
<?php
|
||||
$is_first = true;
|
||||
foreach ( $available_profiles as $key => $label ) :
|
||||
?>
|
||||
<button data-type="<?php echo esc_attr( $key ); ?>" class="mystat-chart-tab <?php
|
||||
if ( $is_first ) {
|
||||
echo 'active';
|
||||
$is_first = false; }
|
||||
?>
|
||||
"><?php echo esc_html( $label ); ?></button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if ( $has_time_data ) : ?>
|
||||
<div class="mystat-xaxis-switcher" style="padding-top: 5px; font-size: 0.9em; white-space: nowrap;">
|
||||
<strong>Oś X:</strong>
|
||||
<label><input type="radio" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="distance" checked> Dystans</label>
|
||||
|
||||
<label><input type="radio" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="time"> Czas</label>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<input type="hidden" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="distance" checked>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="mystat-chart-wrapper" style="position: relative; height:250px; width:100%;">
|
||||
<canvas id="<?php echo esc_attr( $chart_id ); ?>"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejestruje shortcode [statpress_summary] and [statpress_activity].
|
||||
*/
|
||||
function statpress_register_shortcode() {
|
||||
add_shortcode( 'statpress_summary', 'statpress_shortcode_handler' );
|
||||
add_shortcode( 'statpress_activity', 'statpress_single_activity_shortcode_handler' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Funkcja obsługująca shortcode.
|
||||
* @param array $atts Atrybuty shortcode'u (np. year, month).
|
||||
* @return string HTML do wyświetlenia.
|
||||
*/
|
||||
function statpress_shortcode_handler( $atts ) {
|
||||
wp_enqueue_style( 'statpress-frontend-styles' );
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// Ustawienie domyślnych atrybutów (bieżący rok i miesiąc)
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'year' => current_time( 'Y' ),
|
||||
'month' => current_time( 'n' ),
|
||||
),
|
||||
$atts,
|
||||
'statpress_summary'
|
||||
);
|
||||
|
||||
$year = intval( $atts['year'] );
|
||||
$month = intval( $atts['month'] );
|
||||
|
||||
// Pobieranie danych z bazy
|
||||
$table_activities = $wpdb->prefix . 'statpress_activities';
|
||||
$sql = $wpdb->prepare(
|
||||
"
|
||||
SELECT a.*, c.name as category_name, eq.name as equipment_name
|
||||
FROM $table_activities a
|
||||
LEFT JOIN {$wpdb->prefix}statpress_categories c ON a.category_id = c.id
|
||||
LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
|
||||
WHERE YEAR(a.date) = %d AND MONTH(a.date) = %d
|
||||
ORDER BY a.date ASC
|
||||
",
|
||||
$year,
|
||||
$month
|
||||
);
|
||||
|
||||
$activities = $wpdb->get_results( $sql );
|
||||
|
||||
// Obliczanie podsumowań
|
||||
$total_distance = 0;
|
||||
$total_seconds = 0;
|
||||
foreach ( $activities as $activity ) {
|
||||
$total_distance += $activity->distance;
|
||||
if ( ! empty( $activity->duration ) ) {
|
||||
list($h, $m, $s) = explode( ':', $activity->duration );
|
||||
$total_seconds += $h * 3600 + $m * 60 + $s;
|
||||
}
|
||||
}
|
||||
$hours = floor( $total_seconds / 3600 );
|
||||
$minutes = floor( ( $total_seconds % 3600 ) / 60 );
|
||||
$total_duration_formatted = sprintf( '%d godz. %d min.', $hours, $minutes );
|
||||
|
||||
// Rozpoczęcie buforowania wyjścia
|
||||
ob_start();
|
||||
?>
|
||||
<div class="statpress-shortcode-container">
|
||||
|
||||
<h3>Podsumowanie miesiąca</h3>
|
||||
<table class="statpress-summary-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Całkowity dystans</th>
|
||||
<th>Całkowity czas</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><?php echo number_format( $total_distance, 2, ',', ' ' ); ?> km</td>
|
||||
<td><?php echo esc_html( $total_duration_formatted ); ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Lista aktywności</h3>
|
||||
<table class="statpress-activity-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
<th>Tytuł</th>
|
||||
<th>Kategoria</th>
|
||||
|
||||
<th>Dystans</th>
|
||||
<th>Czas</th>
|
||||
<th>Sprzęt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ( ! empty( $activities ) ) : ?>
|
||||
<?php foreach ( $activities as $row ) : ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html( date_i18n( 'd.m.Y', strtotime( $row->date ) ) ); ?></td>
|
||||
<td><?php echo esc_html( $row->title ); ?></td>
|
||||
<td><?php echo esc_html( $row->category_name ); ?></td>
|
||||
<td><?php echo number_format( $row->distance, 2, ',', ' ' ); ?> km</td>
|
||||
<td><?php echo esc_html( $row->duration ); ?></td>
|
||||
<td><?php echo esc_html( $row->equipment_name ); ?></td>
|
||||
</tr>
|
||||
<?php if ( $row->avg_speed || $row->avg_heart_rate || $row->avg_cadence ) : ?>
|
||||
<tr class="statpress-activity-details-row">
|
||||
<td colspan="6" class="statpress-activity-details-cell">
|
||||
<table class="statpress-nested-details-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if ( $row->avg_speed ) { echo '<th>Śr. prędkość</th>'; } ?>
|
||||
<?php if ( $row->avg_heart_rate ) { echo '<th>Śr. tętno</th>'; } ?>
|
||||
<?php if ( $row->avg_cadence ) { echo '<th>Śr. kadencja</th>'; } ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<?php if ( $row->avg_speed ) { echo '<td>' . number_format( $row->avg_speed, 1, ',', ' ' ) . ' km/h</td>'; } ?>
|
||||
<?php if ( $row->avg_heart_rate ) { echo '<td>' . $row->avg_heart_rate . ' bpm</td>'; } ?>
|
||||
<?php if ( $row->avg_cadence ) { echo '<td>' . $row->avg_cadence . ' rpm</td>'; } ?>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<tr>
|
||||
<td colspan="6">Brak aktywności w tym miesiącu.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
// Zwrócenie zawartości bufora
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function statpress_single_activity_shortcode_handler( $atts ) {
|
||||
wp_enqueue_style( 'statpress-frontend-styles' );
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$atts = shortcode_atts(
|
||||
array(
|
||||
'id' => 0,
|
||||
),
|
||||
$atts,
|
||||
'statpress_activity'
|
||||
);
|
||||
|
||||
$activity_id = intval( $atts['id'] );
|
||||
|
||||
if ( 0 === $activity_id ) {
|
||||
return '<p><strong>Błąd:</strong> Nie podano ID wpisu w shortcode.</p>';
|
||||
}
|
||||
|
||||
// Pobieranie danych z bazy
|
||||
$table_activities = $wpdb->prefix . 'statpress_activities';
|
||||
$sql = $wpdb->prepare(
|
||||
"
|
||||
SELECT a.*, c.name as category_name, c.color as category_color, et.name as event_type_name, eq.name as equipment_name
|
||||
FROM $table_activities a
|
||||
LEFT JOIN {$wpdb->prefix}statpress_categories c ON a.category_id = c.id
|
||||
LEFT JOIN {$wpdb->prefix}statpress_event_types et ON a.event_type_id = et.id
|
||||
LEFT JOIN {$wpdb->prefix}statpress_equipment eq ON a.equipment_id = eq.id
|
||||
WHERE a.id = %d
|
||||
",
|
||||
$activity_id
|
||||
);
|
||||
|
||||
$activity = $wpdb->get_row( $sql );
|
||||
|
||||
if ( ! $activity ) {
|
||||
return '<p><strong>Błąd:</strong> Nie znaleziono wpisu o ID ' . esc_html( $activity_id ) . '.</p>';
|
||||
}
|
||||
|
||||
// Funkcja pomocnicza do wyświetlania wiersza
|
||||
$render_row = function( $label, $value, $unit = '' ) {
|
||||
if ( ! is_null( $value ) && '' !== $value && 0 != $value ) {
|
||||
echo '<tr>';
|
||||
echo '<th>' . esc_html( $label ) . '</th>';
|
||||
echo '<td>' . esc_html( $value ) . ( $unit ? ' ' . esc_html( $unit ) : '' ) . '</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
};
|
||||
|
||||
ob_start();
|
||||
|
||||
// Prepare map and chart data if GPX exists
|
||||
$gpx_data = array();
|
||||
$has_gpx_data = false;
|
||||
if ( ! empty( $activity->gpx_url ) ) {
|
||||
$gpx_data = statpress_parse_gpx_data( $activity->gpx_url );
|
||||
$has_gpx_data = ! empty( $gpx_data['points'] );
|
||||
}
|
||||
|
||||
$unique_id = 'statpress-activity-' . esc_attr( $activity->id );
|
||||
|
||||
if ( $has_gpx_data ) {
|
||||
wp_enqueue_style( 'leaflet-css', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' );
|
||||
wp_enqueue_script( 'leaflet-js', 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', array(), '1.9.4', true );
|
||||
wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
|
||||
|
||||
$map_id = 'map-' . $unique_id;
|
||||
$chart_id = 'chart-' . $unique_id;
|
||||
|
||||
$available_profiles = array();
|
||||
if ( ! empty( array_filter( $gpx_data['profiles']['elevation'] ) ) ) {
|
||||
$available_profiles['elevation'] = 'Wysokość';}
|
||||
if ( ! empty( array_filter( $gpx_data['profiles']['speed'] ) ) ) {
|
||||
$available_profiles['speed'] = 'Prędkość';}
|
||||
if ( ! empty( array_filter( $gpx_data['profiles']['hr'] ) ) ) {
|
||||
$available_profiles['hr'] = 'Tętno';}
|
||||
if ( ! empty( array_filter( $gpx_data['profiles']['cadence'] ) ) ) {
|
||||
$available_profiles['cadence'] = 'Kadencja';}
|
||||
|
||||
$has_time_data = ! empty( array_filter( $gpx_data['profiles']['time'], fn( $t ) => ! is_null( $t ) ) );
|
||||
|
||||
wp_register_script( 'statpress-shortcode-loader-' . $activity->id, false );
|
||||
wp_enqueue_script( 'statpress-shortcode-loader-' . $activity->id );
|
||||
|
||||
$js_script = '
|
||||
(function() {
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const uniqueId = "' . esc_js( $unique_id ) . '";
|
||||
const containerEl = document.getElementById(uniqueId);
|
||||
if (!containerEl || typeof L === "undefined" || typeof Chart === "undefined") return;
|
||||
|
||||
const trackPoints = ' . json_encode( $gpx_data['points'] ) . ';
|
||||
const profiles = ' . json_encode( $gpx_data['profiles'] ) . ';
|
||||
let activeChart = null;
|
||||
|
||||
const mapId = "' . esc_js( $map_id ) . '";
|
||||
const mapEl = document.getElementById(mapId);
|
||||
if (mapEl && trackPoints.length > 0) {
|
||||
if (mapEl._leaflet_id) mapEl._leaflet_id = null;
|
||||
const map = L.map(mapId);
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: \'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>\' }).addTo(map);
|
||||
const polyline = L.polyline(trackPoints, {color: "' . esc_js( $activity->category_color ) . '"}).addTo(map);
|
||||
map.fitBounds(polyline.getBounds().pad(0.1));
|
||||
}
|
||||
|
||||
const chartId = "' . esc_js( $chart_id ) . '";
|
||||
const chartEl = document.getElementById(chartId);
|
||||
if (!chartEl) return;
|
||||
|
||||
const chartConfigs = {
|
||||
elevation: { label: "Wysokość", unit: "m n.p.m.", color: "#8e44ad" },
|
||||
speed: { label: "Prędkość", unit: "km/h", color: "#2980b9" },
|
||||
hr: { label: "Tętno", unit: "bpm", color: "#c0392b" },
|
||||
cadence: { label: "Kadencja", unit: "rpm", color: "#27ae60" }
|
||||
};
|
||||
const xAxisConfigs = {
|
||||
distance: { label: "Dystans (km)", data: profiles.distance },
|
||||
time: { label: "Czas", data: profiles.time, formatter: (s) => s === null ? "" : new Date(s * 1000).toISOString().substr(11, 8) }
|
||||
};
|
||||
|
||||
function renderChart() {
|
||||
if (activeChart) activeChart.destroy();
|
||||
const activeTab = containerEl.querySelector(".statpress-chart-tab.active");
|
||||
if (!activeTab) return;
|
||||
const chartType = activeTab.dataset.type;
|
||||
const xAxisRadio = containerEl.querySelector(\'input[name="xaxis-\' + uniqueId + \'"]:checked\');
|
||||
if (!xAxisRadio) return;
|
||||
const xAxisType = xAxisRadio.value;
|
||||
|
||||
const yData = profiles[chartType], xData = xAxisConfigs[xAxisType].data;
|
||||
const filteredY = [], filteredX = [];
|
||||
if(yData) {
|
||||
for(let i=0; i<yData.length; i++) {
|
||||
if (yData[i] !== null) {
|
||||
filteredY.push(yData[i]);
|
||||
filteredX.push(xData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ctx = chartEl.getContext("2d");
|
||||
activeChart = new Chart(ctx, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: filteredX,
|
||||
datasets: [{
|
||||
label: chartConfigs[chartType].label, data: filteredY,
|
||||
borderColor: chartConfigs[chartType].color, backgroundColor: chartConfigs[chartType].color + "20",
|
||||
fill: true, pointRadius: 0, tension: 0.1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: { title: { display: true, text: xAxisConfigs[xAxisType].label }, ticks: { callback: xAxisType === "time" ? xAxisConfigs.time.formatter : (v) => v, maxRotation: 0, autoSkip: true, maxTicksLimit: 7 } },
|
||||
y: { title: { display: true, text: chartConfigs[chartType].unit } }
|
||||
},
|
||||
plugins: { legend: { display: false } },
|
||||
interaction: { intersect: false, mode: "index" },
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
containerEl.querySelectorAll(".statpress-chart-tab").forEach(t => t.addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
containerEl.querySelector(".statpress-chart-tab.active").classList.remove("active");
|
||||
e.currentTarget.classList.add("active");
|
||||
renderChart();
|
||||
}));
|
||||
containerEl.querySelectorAll(\'input[name="xaxis-\' + uniqueId + \'"]\').forEach(r => r.addEventListener("change", renderChart));
|
||||
if (containerEl.querySelector(".statpress-chart-tab")) renderChart();
|
||||
});
|
||||
})();';
|
||||
wp_add_inline_script( 'statpress-shortcode-loader-' . $activity->id, $js_script );
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="statpress-single-activity-shortcode" id="<?php echo esc_attr( $unique_id ); ?>">
|
||||
<h4><?php echo esc_html( $activity->title ); ?></h4>
|
||||
<p><em><?php echo esc_html( date_i18n( 'j F Y', strtotime( $activity->date ) ) ); ?></em></p>
|
||||
|
||||
<div class="statpress-single-columns-container">
|
||||
<div class="statpress-single-col">
|
||||
<table class="statpress-single-summary-table">
|
||||
<tbody>
|
||||
<?php $render_row( 'Dystans', number_format( $activity->distance, 2, ',', ' ' ), 'km' ); ?>
|
||||
<?php $render_row( 'Czas trwania', $activity->duration ); ?>
|
||||
<?php $render_row( 'Średnia prędkość', number_format( $activity->avg_speed, 1, ',', ' ' ), 'km/h' ); ?>
|
||||
<?php $render_row( 'Suma wzniosów', $activity->total_elevation_gain, 'm' ); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="statpress-single-col">
|
||||
<table class="statpress-single-summary-table">
|
||||
<tbody>
|
||||
<?php $render_row( 'Kategoria', $activity->category_name ); ?>
|
||||
<?php $render_row( 'Sprzęt', $activity->equipment_name ); ?>
|
||||
<?php if ( ! empty( $activity->strava_url ) ) : ?>
|
||||
<tr><th>Strava</th><td><a href="<?php echo esc_url( $activity->strava_url ); ?>" target="_blank" rel="noopener noreferrer">Zobacz aktywność</a></td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ( $has_gpx_data ) : ?>
|
||||
<div id="<?php echo esc_attr( $map_id ); ?>" class="statpress-single-map" style="height: 350px; width: 100%; margin-top: 15px; border-radius: 5px; margin-bottom: 20px;"></div>
|
||||
|
||||
<?php if ( ! empty( $available_profiles ) ) : ?>
|
||||
<div class="statpress-charts-container">
|
||||
<div class="statpress-chart-controls" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; margin-bottom: 15px; border-bottom: 1px solid #eee; padding-bottom: 10px; gap: 10px;">
|
||||
<div class="statpress-chart-tabs" style="display: flex; flex-wrap: wrap; gap: 5px;">
|
||||
<?php
|
||||
$is_first = true;
|
||||
foreach ( $available_profiles as $key => $label ) :
|
||||
?>
|
||||
<button data-type="<?php echo esc_attr( $key ); ?>" class="statpress-chart-tab <?php
|
||||
if ( $is_first ) {
|
||||
echo 'active';
|
||||
$is_first = false; }
|
||||
?>
|
||||
"><?php echo esc_html( $label ); ?></button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if ( $has_time_data ) : ?>
|
||||
<div class="statpress-xaxis-switcher" style="padding-top: 5px; font-size: 0.9em; white-space: nowrap;">
|
||||
<strong>Oś X:</strong>
|
||||
<label><input type="radio" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="distance" checked> Dystans</label>
|
||||
|
||||
<label><input type="radio" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="time"> Czas</label>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<input type="hidden" name="xaxis-<?php echo esc_attr( $unique_id ); ?>" value="distance" checked>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="statpress-chart-wrapper" style="position: relative; height:250px; width:100%;">
|
||||
<canvas id="<?php echo esc_attr( $chart_id ); ?>"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
Reference in New Issue
Block a user