251 lines
13 KiB
HTML
251 lines
13 KiB
HTML
{{ template "header.html" . }}
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const button = document.getElementById("filterDropdownButton");
|
|
const menu = document.getElementById("filterDropdownMenu");
|
|
|
|
button.addEventListener("click", () => {
|
|
menu.classList.toggle("hidden");
|
|
});
|
|
|
|
// Cerrar el menú si haces clic fuera de él
|
|
document.addEventListener("click", (event) => {
|
|
if (!button.contains(event.target) && !menu.contains(event.target)) {
|
|
menu.classList.add("hidden");
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
|
|
<div class="container mx-auto my-8 p-6 bg-white rounded-lg shadow-lg">
|
|
|
|
<div class="flex justify-between items-center mb-4">
|
|
<!-- Dropdown de filtros -->
|
|
<div class="relative">
|
|
<button id="filterDropdownButton"
|
|
class="inline-flex items-center px-4 py-2 bg-blue-500 text-white text-sm font-medium rounded hover:bg-blue-600">
|
|
Filtrar por
|
|
<svg class="w-4 h-4 ml-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M5.293 9.707a1 1 0 011.414 0L10 13.414l3.293-3.707a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
</svg>
|
|
</button>
|
|
<div id="filterDropdownMenu"
|
|
class="hidden absolute mt-2 w-48 bg-white rounded shadow-lg border">
|
|
<a href="/?filter=today" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Hoy</a>
|
|
<a href="/?filter=yesterday" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Ayer</a>
|
|
<a href="/?filter=this-week" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Esta semana</a>
|
|
<a href="/?filter=this-month" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Este mes</a>
|
|
<a href="/?filter=this-year" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Este año</a>
|
|
<a href="/?startDate=2022-10-01&endDate=2023-05-31" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">2022-2023</a>
|
|
<a href="/?startDate=2023-10-01&endDate=2024-05-31" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">2023-2024</a>
|
|
<a href="/?startDate=2024-10-01&endDate=2025-05-31" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">2024-2025</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filtros por fechas -->
|
|
<div class="flex space-x-4">
|
|
<form action="/" method="GET" class="flex space-x-2 items-center">
|
|
<div>
|
|
<input type="date" id="startDate" name="startDate" class="px-4 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 h-10" />
|
|
</div>
|
|
|
|
<div>
|
|
<input type="date" id="endDate" name="endDate" class="px-4 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 h-10" />
|
|
</div>
|
|
|
|
<button type="submit" class="px-4 py-2 bg-blue-500 text-white text-sm font-medium rounded hover:bg-blue-600 h-10">
|
|
Filtrar fechas
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Campo para búsquedas (opcional), visible solo en pantallas grandes -->
|
|
<div class="hidden lg:block">
|
|
<form action="/" method="GET">
|
|
<input type="text" name="search"
|
|
placeholder="Buscar eventos..."
|
|
class="px-4 py-2 border border-gray-300 rounded w-64 focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
<button type="submit" class="px-4 py-2 bg-blue-500 text-white text-sm font-medium rounded hover:bg-blue-600">
|
|
Buscar
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="summary bg-gray-100 border-l-4 border-blue-500 p-4 rounded-lg shadow-lg mb-4">
|
|
<p class="text-xl font-semibold text-gray-700">
|
|
Total de horas encendidas: <span class="text-blue-600">{{ .totalHours }} horas</span> y
|
|
<span class="text-blue-600">{{ .totalMinutes }} minutos</span>
|
|
</p>
|
|
</div>
|
|
|
|
<table class="min-w-full divide-y divide-gray-200 border border-gray-300">
|
|
<thead class="bg-blue-600 text-white">
|
|
<tr>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">ID</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium
|
|
uppercase tracking-wider">Fecha</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium
|
|
uppercase tracking-wider">Stamp</th>
|
|
<th scope="col" class="px-6 py-3 text-left text-xs font-medium
|
|
uppercase tracking-wider">Acción</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
{{range .events}}
|
|
<tr class="hover:bg-gray-100">
|
|
<td class="px-6 py-4 text-sm text-gray-700">{{.ID}}</td>
|
|
<td class="px-6 py-4 text-sm text-gray-700">{{.Timestamp | formatTimestamp }}</td>
|
|
<td class="px-6 py-4 text-sm text-gray-700">{{.EventType}}</td>
|
|
<td class="px-6 py-4 text-sm text-gray-700">
|
|
<a href="/events/edit/{{.ID}}">Editar</a>
|
|
<form action="/events/del/{{.ID}}" method="POST" onsubmit="return confirm('¿Estás seguro de que deseas eliminar este evento?');">
|
|
<input type="hidden" name="_method" value="DELETE">
|
|
<button type="submit" class="text-red-600 hover:underline">Eliminar</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
<tfoot class="bg-blue-600 text-white">
|
|
<tr>
|
|
<td colspan="4" class="px-6 py-3 text-center">
|
|
<div class="flex items-center justify-between">
|
|
<!-- Mostrando registros -->
|
|
<span class="text-sm text-white opacity-80">Mostrando {{len .events}}
|
|
eventos de {{ .count }}</span>
|
|
|
|
<!-- Paginador -->
|
|
<div class="flex space-x-2">
|
|
<!-- Primera página -->
|
|
<a href="/?page=1&size={{.size}}{{if .search}}&search={{.search}}{{end}}{{if .startDate}}&startDate={{.startDate}}{{end}}{{if .endDate}}&endDate={{.endDate}}{{end}}{{if .filter}}&filter={{.filter}}{{end}}"
|
|
class="px-3 py-1 text-sm bg-blue-500 text-white
|
|
rounded hover:bg-blue-600 {{if eq .page 1}}cursor-not-allowed opacity-50{{end}}">
|
|
Primera
|
|
</a>
|
|
|
|
<!-- Página anterior -->
|
|
<a href="/?page={{.prevPage}}&size={{.size}}{{if .search}}&search={{.search}}{{end}}{{if .startDate}}&startDate={{.startDate}}{{end}}{{if .endDate}}&endDate={{.endDate}}{{end}}{{if .filter}}&filter={{.filter}}{{end}}"
|
|
class="px-3 py-1 text-sm bg-blue-500 text-white
|
|
rounded hover:bg-blue-600 {{if eq .page 1}}cursor-not-allowed opacity-50{{end}}">
|
|
Anterior
|
|
</a>
|
|
|
|
<!-- Información de la página actual -->
|
|
<span class="px-3 py-1 text-sm text-white opacity-80">
|
|
Página {{.page}} de {{.totalPages}}
|
|
</span>
|
|
|
|
<!-- Página siguiente -->
|
|
<a href="/?page={{.nextPage}}&size={{.size}}{{if .search}}&search={{.search}}{{end}}{{if .startDate}}&startDate={{.startDate}}{{end}}{{if .endDate}}&endDate={{.endDate}}{{end}}{{if .filter}}&filter={{.filter}}{{end}}"
|
|
class="px-3 py-1 text-sm bg-blue-500 text-white
|
|
rounded hover:bg-blue-600 {{if eq .page .totalPages}}cursor-not-allowed opacity-50{{end}}">
|
|
Siguiente
|
|
</a>
|
|
|
|
<!-- Última página -->
|
|
<a href="/?page={{.totalPages}}&size={{.size}}{{if .search}}&search={{.search}}{{end}}{{if .startDate}}&startDate={{.startDate}}{{end}}{{if .endDate}}&endDate={{.endDate}}{{end}}{{if .filter}}&filter={{.filter}}{{end}}"
|
|
class="px-3 py-1 text-sm bg-blue-500 text-white
|
|
rounded hover:bg-blue-600 {{if eq .page .totalPages}}cursor-not-allowed opacity-50{{end}}">
|
|
Última
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
|
|
<canvas id="myChart" class="w-full lg:w-auto"></canvas>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const ctx = document.getElementById('myChart').getContext('2d');
|
|
|
|
// Datos sin ordenar
|
|
const labels = {{ .labels | json }};
|
|
const data = {{ .totals | json }};
|
|
|
|
// Función para convertir el formato "Month Year" a "YYYY-MM"
|
|
function formatMonthYearToDate(monthYear) {
|
|
const [month, year] = monthYear.split(" ");
|
|
const monthIndex = new Date(`${month} 1`).getMonth();
|
|
return `${year}-${String(monthIndex + 1).padStart(2, "0")}`;
|
|
}
|
|
|
|
// Función para asegurarnos de que las fechas estén en un formato "YYYY-MM-DD"
|
|
function formatDateForSorting(dateString) {
|
|
if (dateString.includes("-")) {
|
|
// Si ya está en formato YYYY-MM-DD, no es necesario convertirlo
|
|
return dateString;
|
|
} else {
|
|
// Si es formato "Month Year", lo convertimos a "YYYY-MM"
|
|
return formatMonthYearToDate(dateString);
|
|
}
|
|
}
|
|
|
|
// Combina los datos y las fechas en un solo array de objetos
|
|
const combinedData = labels.map((label, index) => ({
|
|
label: label,
|
|
data: data[index],
|
|
sortableDate: formatDateForSorting(label),
|
|
}));
|
|
|
|
// Ordena los datos por la fecha (label)
|
|
combinedData.sort((a, b) => new Date(a.sortableDate) - new Date(b.sortableDate));
|
|
|
|
|
|
// Separa los labels y data ordenados
|
|
const sortedLabels = combinedData.map(item => item.label);
|
|
const sortedData = combinedData.map(item => item.data);
|
|
|
|
new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: sortedLabels,
|
|
datasets: [{
|
|
label: 'Consumo (horas)',
|
|
data: sortedData,
|
|
borderColor: 'rgba(75, 192, 192, 1)',
|
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
const value = context.raw; // Valor numérico del consumo
|
|
const hours = Math.floor(value); // Parte entera (horas)
|
|
const minutes = Math.round((value - hours) * 60); // Parte decimal convertida a minutos
|
|
return `Consumo (horas): ${hours}h ${minutes}m`; // Formato personalizado
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: 'Día'
|
|
}
|
|
},
|
|
y: {
|
|
title: {
|
|
display: true,
|
|
text: 'Consumo (horas)'
|
|
},
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
{{ template "footer.html" . }}
|