mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-22 08:40:22 +00:00
falta borrar y busqueda por columnas
This commit is contained in:
@ -53,4 +53,9 @@ spring.web.resources.chain.strategy.content.paths=/assets/**
|
||||
#
|
||||
server.servlet.session.timeout=30m
|
||||
|
||||
security.rememberme.key=N`BY^YRVO:/\H$hsKxNq
|
||||
security.rememberme.key=N`BY^YRVO:/\H$hsKxNq
|
||||
|
||||
#
|
||||
# Enable HiddenHttpMethodFilter to support PUT and DELETE methods in forms
|
||||
#
|
||||
spring.mvc.hiddenmethod.filter.enabled=true
|
||||
@ -1,6 +1,12 @@
|
||||
usuarios.titulo=Usuarios
|
||||
usuarios.nuevo=Nuevo usuario
|
||||
usuarios.editar=Editar usuario
|
||||
usuarios.add=Añadir usuario
|
||||
usuarios.eliminar=Eliminar usuario
|
||||
usuarios.confirmarEliminar=¿Está seguro de que desea eliminar este usuario?
|
||||
usuarios.guardar=Guardar
|
||||
|
||||
usuarios.tabla.id=ID
|
||||
usuarios.tabla.nombre=Nombre
|
||||
usuarios.tabla.email=Correo electrónico
|
||||
usuarios.tabla.rol=Rol
|
||||
@ -8,3 +14,31 @@ usuarios.tabla.estado=Estado
|
||||
usuarios.tabla.acciones=Acciones
|
||||
usuarios.tabla.activo=Activo
|
||||
usuarios.tabla.inactivo=Inactivo
|
||||
|
||||
usuarios.form.nombre=Nombre completo
|
||||
usuarios.form.email=Correo electrónico
|
||||
usuarios.form.password=Contraseña
|
||||
usuarios.form.confirmarPassword=Confirmar contraseña
|
||||
usuarios.form.rol=Rol
|
||||
usuarios.form.estado=Estado
|
||||
|
||||
usuarios.rol.user=Usuario
|
||||
usuarios.rol.admin=Administrador
|
||||
usuarios.rol.superadmin=Super Administrador
|
||||
|
||||
usuarios.error.duplicado=Ya existe un usuario con este correo electrónico.
|
||||
usuarios.error.general=Se ha producido un error al procesar la solicitud. Por favor, inténtelo de nuevo más tarde.
|
||||
usuarios.error.noEncontrado=Usuario no encontrado.
|
||||
|
||||
usuarios.error.nombre=El nombre es obligatorio.
|
||||
usuarios.error.email=El correo electrónico es obligatorio.
|
||||
usuarios.error.email.formato=El correo electrónico no es válido.
|
||||
usuarios.error.rol=El rol seleccionado no es válido.
|
||||
usuarios.error.password.requerida=La contraseña es obligatoria.
|
||||
usuarios.error.password.min=La contraseña debe tener al menos 6 caracteres.
|
||||
usuarios.error.confirmPassword.requerida=La confirmación de la contraseña es obligatoria.
|
||||
usuarios.error.password-coinciden=Las contraseñas no coinciden.
|
||||
|
||||
usuarios.exito.creado=Usuario creado con éxito.
|
||||
usuarios.exito.actualizado=Usuario actualizado con éxito.
|
||||
usuarios.exito.eliminado=Usuario eliminado con éxito.
|
||||
@ -4,5 +4,7 @@ validation.min=El valor mínimo es {value}
|
||||
validation.max=El valor máximo es {value}
|
||||
validation.typeMismatchMsg=Tipo de dato no válido
|
||||
validation.patternMsg=El formato no es válido
|
||||
validation.unique=El valor ya existe y debe ser único
|
||||
validation.email=El correo electrónico no es válido
|
||||
|
||||
|
||||
|
||||
@ -1,29 +1,84 @@
|
||||
$(() => {
|
||||
const language = document.documentElement.lang || 'es-ES';
|
||||
|
||||
|
||||
const table = new DataTable('#users-datatable', {
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
language: {
|
||||
url: '/assets/libs/datatables/i18n/' + language + '.json'
|
||||
},
|
||||
processing: true, serverSide: true, pageLength: 50,
|
||||
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
|
||||
responsive: true,
|
||||
ajax: {
|
||||
url: '/users/datatable',
|
||||
method: 'GET',
|
||||
data: d => { /* extra params si quieres */ }
|
||||
},
|
||||
order: [[0, 'asc']],
|
||||
ajax: { url: '/users/datatable', method: 'GET' },
|
||||
order: [[0,'asc']],
|
||||
columns: [
|
||||
{ data: 'fullName', name: 'fullname' },
|
||||
{ data: 'userName', name: 'username' },
|
||||
{ data: 'roles', name: 'roles' },
|
||||
{ data: 'id', name: 'id' , orderable: true },
|
||||
{ data: 'fullName', name: 'fullName' , orderable: true },
|
||||
{ data: 'userName', name: 'userName' , orderable: true },
|
||||
{ data: 'roles', name: 'roleRank' },
|
||||
{ data: 'enabled', name: 'enabled', searchable: false },
|
||||
{ data: 'actions', name: 'actions' }
|
||||
],
|
||||
columnDefs: [
|
||||
// Desactiva orden y búsqueda en la columna de acciones
|
||||
{ targets: -1, orderable: false, searchable: false }
|
||||
]
|
||||
columnDefs: [{ targets: -1, orderable: false, searchable: false }]
|
||||
});
|
||||
|
||||
const modalEl = document.getElementById('userFormModal');
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
|
||||
// Abrir "Crear"
|
||||
$('#addUserButton').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
$.get('/users/form', function (html) {
|
||||
$('#userModalBody').html(html);
|
||||
const title = $('#userModalBody #userForm').data('add');
|
||||
$('#userFormModal .modal-title').text(title);
|
||||
modal.show();
|
||||
});
|
||||
});
|
||||
|
||||
// Abrir "Editar"
|
||||
$(document).on('click', '.btn-edit-user', function (e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
$.get('/users/form', { id }, function (html) {
|
||||
$('#userModalBody').html(html);
|
||||
const title = $('#userModalBody #userForm').data('edit');
|
||||
$('#userFormModal .modal-title').text(title);
|
||||
modal.show();
|
||||
});
|
||||
});
|
||||
|
||||
// Submit del form en el modal
|
||||
$(document).on('submit', '#userForm', function (e) {
|
||||
e.preventDefault();
|
||||
const $form = $(this);
|
||||
|
||||
$.ajax({
|
||||
url: $form.attr('action'),
|
||||
type: 'POST', // PUT simulado via _method
|
||||
data: $form.serialize(),
|
||||
dataType: 'html',
|
||||
success: function (html) {
|
||||
// Si por cualquier motivo llega 200 con fragmento, lo insertamos igual
|
||||
if (typeof html === 'string' && html.indexOf('id="userForm"') !== -1 && html.indexOf('<html') === -1) {
|
||||
$('#userModalBody').html(html);
|
||||
const isEdit = $('#userModalBody #userForm input[name="_method"][value="PUT"]').length > 0;
|
||||
const title = $('#userModalBody #userForm').data(isEdit ? 'edit' : 'add');
|
||||
$('#userFormModal .modal-title').text(title);
|
||||
return;
|
||||
}
|
||||
// Éxito real: cerrar y recargar tabla
|
||||
modal.hide();
|
||||
table.ajax.reload(null, false);
|
||||
},
|
||||
error: function (xhr) {
|
||||
// Con 422 devolvemos el fragmento con errores aquí
|
||||
if (xhr.status === 422 && xhr.responseText) {
|
||||
$('#userModalBody').html(xhr.responseText);
|
||||
const isEdit = $('#userModalBody #userForm input[name="_method"][value="PUT"]').length > 0;
|
||||
const title = $('#userModalBody #userForm').data(isEdit ? 'edit' : 'add');
|
||||
$('#userFormModal .modal-title').text(title);
|
||||
return;
|
||||
}
|
||||
// Fallback
|
||||
$('#userModalBody').html('<div class="p-3 text-danger">Error inesperado.</div>');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
|
||||
</div>
|
||||
<div class="modal-body" th:id="${bodyId}">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
<div th:fragment="userForm">
|
||||
<form id="userForm" novalidate th:action="${action}" th:object="${user}" method="post" th:data-add="#{usuarios.add}"
|
||||
th:data-edit="#{usuarios.editar}">
|
||||
|
||||
<input type="hidden" name="_method" value="PUT" th:if="${user.id != null}" />
|
||||
|
||||
<div th:if="${#fields.hasGlobalErrors()}" class="alert alert-danger">
|
||||
<div th:each="e : ${#fields.globalErrors()}" th:text="${e}"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label th:text="#{usuarios.form.nombre}" for="nombre">Nombre</label>
|
||||
<input type="text" class="form-control" id="nombre" th:field="*{fullName}"
|
||||
th:classappend="${#fields.hasErrors('fullName')} ? ' is-invalid'" required>
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('fullName')}" th:errors="*{fullName}">Error</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{usuarios.form.email}" for="email">Correo electrónico</label>
|
||||
<input type="email" class="form-control" id="email" th:field="*{userName}"
|
||||
th:classappend="${#fields.hasErrors('userName')} ? ' is-invalid'" required>
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('userName')}" th:errors="*{userName}">Error</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{usuarios.form.password}" for="password">Contraseña</label>
|
||||
<input type="password" class="form-control" id="password" th:field="*{password}" minlength="6"
|
||||
th:attr="required=${user.id == null}" th:classappend="${#fields.hasErrors('password')} ? ' is-invalid'">
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('password')}" th:errors="*{password}">Error</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{usuarios.form.confirmarPassword}" for="confirmPassword">Confirmar Contraseña</label>
|
||||
<input type="password" class="form-control" id="confirmPassword" th:field="*{confirmPassword}" minlength="6"
|
||||
th:attr="required=${user.id == null}"
|
||||
th:classappend="${#fields.hasErrors('confirmPassword')} ? ' is-invalid'">
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('confirmPassword')}"
|
||||
th:errors="*{confirmPassword}">Error</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{usuarios.form.rol}" for="rol">Rol</label>
|
||||
<select class="form-control" id="rol" th:field="*{roleName}" required
|
||||
th:classappend="${#fields.hasErrors('roleName')} ? ' is-invalid'">
|
||||
<option value="USER" selected>Usuario</option>
|
||||
<option value="ADMIN">Administrador</option>
|
||||
<option value="SUPERADMIN">Super Administrador</option>
|
||||
</select>
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('roleName')}" th:errors="*{roleName}">Error</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label th:text="#{usuarios.form.estado}" for="estado">Estado</label>
|
||||
<select class="form-control" id="estado" th:field="*{enabled}" required
|
||||
th:classappend="${#fields.hasErrors('enabled')} ? ' is-invalid'">
|
||||
<option th:value="true" th:selected="${user.id == null or user.enabled}">Activo</option>
|
||||
<option th:value="false" th:selected="${user.id != null and !user.enabled}">Inactivo</option>
|
||||
</select>
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('enabled')}" th:errors="*{enabled}">Error</div>
|
||||
</div>
|
||||
<div class="row mt-3 justified-content-center d-flex">
|
||||
<button type="submit" class="btn btn-secondary" th:text="#{usuarios.guardar}">Guardar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -21,6 +21,11 @@
|
||||
<th:block layout:fragment="content">
|
||||
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||
|
||||
<!-- Modales-->
|
||||
<div
|
||||
th:replace="imprimelibros/partials/modal-form :: modal('userFormModal', 'usuarios.add', 'modal-md', 'userModalBody')">
|
||||
</div>
|
||||
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/"><i class="ri-home-5-fill"></i></a></li>
|
||||
@ -30,9 +35,14 @@
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<button type="button" class="btn btn-secondary mb-3" id="addUserButton">
|
||||
<i class="ri-add-line align-bottom me-1"></i> <span th:text="#{usuarios.add}">Añadir usuario</span>
|
||||
</button>
|
||||
|
||||
<table id="users-datatable" class="table table-striped table-nowrap responsive w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" th:text="#{usuarios.tabla.id}">ID</th>
|
||||
<th scope="col" th:text="#{usuarios.tabla.nombre}">Nombre</th>
|
||||
<th scope="col" th:text="#{usuarios.tabla.email}">Correo electrónico</th>
|
||||
<th scope="col" th:text="#{usuarios.tabla.rol}">Rol</th>
|
||||
|
||||
Reference in New Issue
Block a user