199 lines
8.7 KiB
HTML
199 lines
8.7 KiB
HTML
<!DOCTYPE html>
|
|
<html xmlns:th="http://www.thymeleaf.org">
|
|
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Dashboard | Weight Tracker</title>
|
|
<link th:href="@{/vendor/fontawesome-free/css/all.min.css}" rel="stylesheet">
|
|
<link th:href="@{/css/sb-admin-2.min.css}" rel="stylesheet">
|
|
<link rel="icon" href="data:,">
|
|
|
|
<!-- Chart.js -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<!-- Daterangepicker -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
|
|
</head>
|
|
|
|
<body id="page-top">
|
|
<div id="wrapper">
|
|
<div th:replace="~{fragments/fragment-menu :: menu}"></div>
|
|
<div id="content-wrapper" class="d-flex flex-column">
|
|
<div id="content" class="p-4">
|
|
<nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
|
|
<button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3">
|
|
<i class="fa fa-bars"></i>
|
|
</button>
|
|
<h1 class="h4 mb-0 text-gray-800">Dashboard</h1>
|
|
</nav>
|
|
|
|
<div class="container-fluid">
|
|
<div class="row gx-3 gy-4">
|
|
<!-- Últimos registros -->
|
|
<div class="col-12 col-lg-6">
|
|
<div class="card shadow mb-4 h-100">
|
|
<div class="card-header">Últimos 10 registros</div>
|
|
<div class="card-body">
|
|
<table class="table table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th>Fecha</th>
|
|
<th>Peso (kg)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr th:each="r : ${ultimos}">
|
|
<td th:text="${#temporals.format(r.date, 'yyyy-MM-dd HH:mm')}">2025-07-10</td>
|
|
<td th:text="${r.weight}">70.5</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gráfica -->
|
|
<div class="col-12 col-lg-6 mt-3 mt-lg-0">
|
|
<div class="card shadow mb-4 h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span id="chart-title">Evolución del peso</span>
|
|
<div class="dropdown">
|
|
<button class="btn btn-sm btn-secondary dropdown-toggle" type="button" id="metricDropdown"
|
|
data-bs-toggle="dropdown" aria-expanded="false">
|
|
Métricas
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end p-2" aria-labelledby="metricDropdown"
|
|
style="min-width: 200px;">
|
|
<li><div class="form-check"><input class="form-check-input metric-check" type="checkbox" value="weight" id="metricWeight" checked><label class="form-check-label" for="metricWeight">Peso (kg)</label></div></li>
|
|
<li><div class="form-check"><input class="form-check-input metric-check" type="checkbox" value="bodyFat" id="metricBodyFat"><label class="form-check-label" for="metricBodyFat">% Grasa</label></div></li>
|
|
<li><div class="form-check"><input class="form-check-input metric-check" type="checkbox" value="muscleMass" id="metricMuscleMass"><label class="form-check-label" for="metricMuscleMass">Masa muscular</label></div></li>
|
|
<li><div class="form-check"><input class="form-check-input metric-check" type="checkbox" value="waterPercent" id="metricWater"><label class="form-check-label" for="metricWater">% Agua</label></div></li>
|
|
<li><div class="form-check"><input class="form-check-input metric-check" type="checkbox" value="bmi" id="metricBMI"><label class="form-check-label" for="metricBMI">IMC</label></div></li>
|
|
<li><div class="form-check"><input class="form-check-input metric-check" type="checkbox" value="metabolicAge" id="metricMetAge"><label class="form-check-label" for="metricMetAge">Edad metabólica</label></div></li>
|
|
<li><div class="form-check"><input class="form-check-input metric-check" type="checkbox" value="visceralFat" id="metricVisceral"><label class="form-check-label" for="metricVisceral">Grasa visceral</label></div></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-2">
|
|
<label for="chartDateRange">Filtrar por fecha:</label>
|
|
<input type="text" id="chartDateRange" class="form-control form-control-sm w-auto d-inline-block">
|
|
</div>
|
|
<canvas id="chart" class="w-100" style="max-height: 300px;"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- jQuery, Bootstrap y plugins -->
|
|
<script th:src="@{/vendor/jquery/jquery.min.js}"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
|
|
<script th:src="@{/js/sb-admin-2.min.js}"></script>
|
|
|
|
<!-- Script Chart -->
|
|
<script th:inline="javascript">
|
|
$(document).ready(function () {
|
|
// Datos desde el controlador
|
|
const labels = /*[[${labels}]]*/[];
|
|
const dataMap = {
|
|
weight: /*[[${pesos}]]*/[],
|
|
bodyFat: /*[[${grasa}]]*/[],
|
|
muscleMass: /*[[${musculo}]]*/[],
|
|
waterPercent: /*[[${agua}]]*/[],
|
|
bmi: /*[[${bmi}]]*/[],
|
|
metabolicAge: /*[[${edadMetabolica}]]*/[],
|
|
visceralFat: /*[[${grasaVisceral}]]*/[]
|
|
};
|
|
|
|
const metricColors = {
|
|
weight: 'rgba(78, 115, 223, 1)',
|
|
bodyFat: 'rgba(231, 74, 59, 1)',
|
|
muscleMass: 'rgba(28, 200, 138, 1)',
|
|
waterPercent: 'rgba(54, 185, 204, 1)',
|
|
bmi: 'rgba(246, 194, 62, 1)',
|
|
metabolicAge: 'rgba(133, 135, 150, 1)',
|
|
visceralFat: 'rgba(106, 90, 205, 1)'
|
|
};
|
|
|
|
const ctx = document.getElementById('chart').getContext('2d');
|
|
const chart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: 'Peso (kg)',
|
|
data: dataMap.weight,
|
|
borderColor: metricColors.weight,
|
|
borderWidth: 2,
|
|
fill: false,
|
|
tension: 0.3,
|
|
pointRadius: 3
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: {
|
|
ticks: {
|
|
maxRotation: 45,
|
|
minRotation: 45
|
|
}
|
|
},
|
|
y: {
|
|
beginAtZero: false
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Filtro por métricas
|
|
$('.metric-check').on('change', function () {
|
|
const selected = $('.metric-check:checked').map(function () {
|
|
return this.value;
|
|
}).get();
|
|
|
|
chart.data.datasets = selected.map(metric => ({
|
|
label: $(`label[for="metric${metric.charAt(0).toUpperCase() + metric.slice(1)}"]`).text(),
|
|
data: dataMap[metric],
|
|
borderColor: metricColors[metric],
|
|
borderWidth: 2,
|
|
fill: false,
|
|
tension: 0.3,
|
|
pointRadius: 3
|
|
}));
|
|
|
|
chart.update();
|
|
});
|
|
|
|
// Rango de fechas
|
|
$('#chartDateRange').daterangepicker({
|
|
autoUpdateInput: false,
|
|
locale: {
|
|
cancelLabel: 'Limpiar',
|
|
applyLabel: 'Aplicar',
|
|
format: 'YYYY-MM-DD'
|
|
}
|
|
});
|
|
|
|
$('#chartDateRange').on('apply.daterangepicker', function (ev, picker) {
|
|
const start = picker.startDate.format('YYYY-MM-DD');
|
|
const end = picker.endDate.format('YYYY-MM-DD');
|
|
window.location.href = `/?start=${start}&end=${end}`;
|
|
});
|
|
|
|
$('#chartDateRange').on('cancel.daterangepicker', function () {
|
|
window.location.href = `/`;
|
|
});
|
|
});
|
|
</script>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
|
|
</html>
|