first commit. application working
This commit is contained in:
198
backend/target/classes/templates/dashboard.html
Normal file
198
backend/target/classes/templates/dashboard.html
Normal file
@ -0,0 +1,198 @@
|
||||
<!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>
|
||||
@ -0,0 +1,51 @@
|
||||
|
||||
<!-- src/main/resources/templates/fragments/fragment-menu.html -->
|
||||
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion toggled" id="accordionSidebar" th:fragment="menu">
|
||||
|
||||
<!-- Sidebar - Brand -->
|
||||
<a class="sidebar-brand d-flex align-items-center justify-content-center" th:href="@{/}">
|
||||
<div class="sidebar-brand-icon">
|
||||
<i class="fas fa-weight"></i>
|
||||
</div>
|
||||
<div class="sidebar-brand-text mx-3">Weight Tracker</div>
|
||||
</a>
|
||||
|
||||
<!-- Divider -->
|
||||
<hr class="sidebar-divider my-0">
|
||||
|
||||
<!-- Nav Items -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" th:href="@{/registros}">
|
||||
<i class="fas fa-list"></i>
|
||||
<span>Ver registros</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" th:href="@{/registros/nuevo}">
|
||||
<i class="fas fa-plus"></i>
|
||||
<span>Añadir registro</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" th:if="${#authorization.expression('hasRole(''ADMIN'')')}">
|
||||
<a class="nav-link" th:href="@{/usuarios}">
|
||||
<i class="fas fa-users-cog"></i>
|
||||
<span>Usuarios</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Logout -->
|
||||
<li class="nav-item">
|
||||
<form th:action="@{/logout}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
<button type="submit" class="nav-link btn btn-link text-start">
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
<span>Cerrar sesión</span>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
<!-- Sidebar Toggler (hamburguesa) -->
|
||||
<div class="text-center d-md-inline">
|
||||
<button class="rounded-circle border-0" id="sidebarToggle"></button>
|
||||
</div>
|
||||
</ul>
|
||||
34
backend/target/classes/templates/login.html
Normal file
34
backend/target/classes/templates/login.html
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Login | Weight Tracker</title>
|
||||
<link th:href="@{/css/sb-admin-2.min.css}" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-gradient-primary d-flex align-items-center" style="height:100vh;">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<h4 class="text-center mb-3">Iniciar sesión</h4>
|
||||
<form th:action="@{/do-login}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
|
||||
<div class="mb-3">
|
||||
<input class="form-control" name="username" placeholder="Usuario" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input class="form-control" type="password" name="password" placeholder="Contraseña" required>
|
||||
</div>
|
||||
<button class="btn btn-primary w-100" type="submit">Entrar</button>
|
||||
</form>
|
||||
<div class="text-danger mt-2" th:if="${param.error}">Credenciales incorrectas</div>
|
||||
<div class="text-success mt-2" th:if="${param.logout}">Sesión cerrada</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script th:src="@{/js/sb-admin-2.min.js}" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
110
backend/target/classes/templates/registro-form.html
Normal file
110
backend/target/classes/templates/registro-form.html
Normal file
@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Añadir registro | Weight Tracker</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<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:,">
|
||||
|
||||
<!-- Flatpickr CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/plugins/confirmDate/confirmDate.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">Añadir registro</h1>
|
||||
</nav>
|
||||
|
||||
<form th:action="@{/registros/guardar}" th:object="${registro}" method="post" class="row g-3">
|
||||
<input type="hidden" th:field="*{id}" />
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Fecha y hora</label>
|
||||
<input type="datetime-local" th:field="*{date}" class="form-control" required />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Peso (kg)</label>
|
||||
<input type="number" step="0.1" th:field="*{weight}" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">% Grasa corporal</label>
|
||||
<input type="number" step="0.1" th:field="*{bodyFat}" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">% Agua</label>
|
||||
<input type="number" step="0.1" th:field="*{waterPercent}" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Masa muscular (kg)</label>
|
||||
<input type="number" step="0.1" th:field="*{muscleMass}" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Edad metabólica</label>
|
||||
<input type="number" th:field="*{metabolicAge}" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Grasa visceral</label>
|
||||
<input type="number" th:field="*{visceralFat}" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">IMC (BMI)</label>
|
||||
<input type="number" step="0.1" th:field="*{bmi}" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label class="form-label">Notas</label>
|
||||
<textarea th:field="*{notes}" class="form-control" rows="3" placeholder="Observaciones opcionales..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary mt-3">Guardar</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Flatpickr scripts -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/plugins/confirmDate/confirmDate.js"></script>
|
||||
<script>
|
||||
flatpickr("input[type=datetime-local]", {
|
||||
enableTime: true,
|
||||
time_24hr: true,
|
||||
dateFormat: "Y-m-d\\TH:i",
|
||||
plugins: [new confirmDatePlugin({
|
||||
confirmText: "OK",
|
||||
showAlways: false,
|
||||
theme: "light",
|
||||
confirmIcon: "",
|
||||
showNow: true,
|
||||
nowText: "Ahora"
|
||||
})]
|
||||
});
|
||||
</script>
|
||||
|
||||
<script th:src="@{/vendor/jquery/jquery.min.js}"></script>
|
||||
<script th:src="@{/vendor/bootstrap/js/bootstrap.bundle.min.js}"></script>
|
||||
<script th:src="@{/js/sb-admin-2.min.js}"></script>
|
||||
</body>
|
||||
</html>
|
||||
121
backend/target/classes/templates/registros.html
Normal file
121
backend/target/classes/templates/registros.html
Normal file
@ -0,0 +1,121 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Registros | Weight Tracker</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<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="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
|
||||
<link rel="icon" href="data:,">
|
||||
</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">Registros</h1>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
<a class="btn btn-primary mb-3" th:href="@{/registros/nuevo}">Nuevo</a>
|
||||
|
||||
<div class="mb-2">
|
||||
<label>Filtrar por fecha:</label>
|
||||
<input type="text" id="dateRange" class="form-control form-control-sm w-auto d-inline-block">
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="tablaRegistros" class="table table-bordered table-striped align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Fecha</th>
|
||||
<th>Peso (kg)</th>
|
||||
<th>% Grasa</th>
|
||||
<th>Masa muscular (kg)</th>
|
||||
<th>% Agua</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="r : ${registros}">
|
||||
<td th:text="${#temporals.format(r.date, 'yyyy-MM-dd HH:mm')}">2025-07-10 18:00</td>
|
||||
<td th:text="${r.weight}">70.5</td>
|
||||
<td th:text="${r.bodyFat}">15.0</td>
|
||||
<td th:text="${r.muscleMass}">55.0</td>
|
||||
<td th:text="${r.waterPercent}">60.0</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-outline-primary" th:href="@{'/registros/' + ${r.id} + '/editar'}">Editar</a>
|
||||
<form th:action="@{'/registros/' + ${r.id} + '/eliminar'}" method="post" class="d-inline">
|
||||
<input type="hidden" name="_method" value="delete" />
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('¿Eliminar registro?')">Eliminar</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script th:src="@{/vendor/jquery/jquery.min.js}"></script>
|
||||
<script th:src="@{/vendor/bootstrap/js/bootstrap.bundle.min.js}"></script>
|
||||
<script th:src="@{/js/sb-admin-2.min.js}"></script>
|
||||
|
||||
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
|
||||
|
||||
<script th:inline="none">
|
||||
$(document).ready(function () {
|
||||
const table = $('#tablaRegistros').DataTable({
|
||||
order: [[0, 'desc']],
|
||||
pageLength: 10,
|
||||
searching: false,
|
||||
lengthMenu: [5, 10, 25, 50, 100],
|
||||
language: {
|
||||
url: 'https://cdn.datatables.net/plug-ins/2.3.2/i18n/es-ES.json',
|
||||
}
|
||||
});
|
||||
|
||||
// Rango de fechas
|
||||
let startDate, endDate;
|
||||
$('#dateRange').daterangepicker({
|
||||
autoUpdateInput: false,
|
||||
locale: {
|
||||
cancelLabel: 'Limpiar',
|
||||
applyLabel: 'Aplicar',
|
||||
format: 'YYYY-MM-DD'
|
||||
}
|
||||
});
|
||||
|
||||
$('#dateRange').on('apply.daterangepicker', function (ev, picker) {
|
||||
startDate = picker.startDate;
|
||||
endDate = picker.endDate;
|
||||
$(this).val(startDate.format('YYYY-MM-DD') + ' - ' + endDate.format('YYYY-MM-DD'));
|
||||
table.draw();
|
||||
});
|
||||
|
||||
$('#dateRange').on('cancel.daterangepicker', function (ev, picker) {
|
||||
$(this).val('');
|
||||
startDate = endDate = null;
|
||||
table.draw();
|
||||
});
|
||||
|
||||
$.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
|
||||
const fecha = moment(data[0], 'YYYY-MM-DD HH:mm');
|
||||
if (!startDate || !endDate) return true;
|
||||
return fecha.isBetween(startDate, endDate, 'day', '[]');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
64
backend/target/classes/templates/usuario-form.html
Normal file
64
backend/target/classes/templates/usuario-form.html
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<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:,">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</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">
|
||||
<!-- Botón hamburguesa solo visible en móvil -->
|
||||
<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">Formulario de usuario</h1>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<h2 class="mb-4" th:text="${usuario.id} != null ? 'Editar usuario' : 'Nuevo usuario'">Nuevo usuario</h2>
|
||||
<form th:action="@{/usuarios/guardar}" th:object="${usuario}" method="post" class="row g-3">
|
||||
<input type="hidden" th:field="*{id}" />
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Usuario</label>
|
||||
<input type="text" th:field="*{username}" class="form-control" required />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Contraseña</label>
|
||||
<input type="password" th:field="*{password}" class="form-control" th:placeholder="${usuario.id} != null ? '••••••••' : ''" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Rol</label>
|
||||
<select th:field="*{role}" class="form-select">
|
||||
<option value="USER">Usuario</option>
|
||||
<option value="ADMIN">Administrador</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary mt-3">Guardar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script th:src="@{/vendor/jquery/jquery.min.js}"></script>
|
||||
<script th:src="@{/vendor/bootstrap/js/bootstrap.bundle.min.js}"></script>
|
||||
<script th:src="@{/js/sb-admin-2.min.js}"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
67
backend/target/classes/templates/usuarios.html
Normal file
67
backend/target/classes/templates/usuarios.html
Normal file
@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<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:,">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</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">
|
||||
<!-- Botón hamburguesa solo visible en móvil -->
|
||||
<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">Usuarios</h1>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<a class="btn btn-primary mb-3" th:href="@{/usuarios/nuevo}">Nuevo</a>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Usuario</th>
|
||||
<th>Rol</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="u : ${usuarios}">
|
||||
<td th:text="${u.username}">usuario</td>
|
||||
<td th:text="${u.role}">ROLE_USER</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-outline-primary" th:href="@{'/usuarios/' + ${u.id} + '/editar'}">Editar</a>
|
||||
<form th:action="@{'/usuarios/' + ${u.id} + '/eliminar'}" method="post" class="d-inline"
|
||||
onsubmit="return confirm('¿Eliminar este usuario y sus registros?');">
|
||||
<input type="hidden" name="_method" value="delete" />
|
||||
<button class="btn btn-sm btn-outline-danger" type="submit">Eliminar</button>
|
||||
</form>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script th:src="@{/vendor/jquery/jquery.min.js}"></script>
|
||||
<script th:src="@{/vendor/bootstrap/js/bootstrap.bundle.min.js}"></script>
|
||||
<script th:src="@{/js/sb-admin-2.min.js}"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user