631 lines
20 KiB
PHP
631 lines
20 KiB
PHP
<?php
|
|
require __DIR__ . '/../config/app.php';
|
|
|
|
require __DIR__ . '/../config/app.php';
|
|
|
|
if (is_logged_in()) {
|
|
header('Location: ' . BASE_URL);
|
|
exit;
|
|
}
|
|
require __DIR__ . '/../partials/header.php';
|
|
?>
|
|
|
|
<link rel="stylesheet" href="../css/views/grupos.css">
|
|
|
|
<div class="container mt-4">
|
|
|
|
<div class="row"> <!-- Grilla -->
|
|
<div class="col-12">
|
|
<div id="pantallaGrilla" class="card shadow-sm rounded-3 overflow-hidden"
|
|
data-aos="fade-up"
|
|
data-aos-duration="600"
|
|
data-aos-easing="ease-out-cubic"
|
|
data-aos-delay="60"
|
|
data-aos-once="false">
|
|
<div class="card-header bg-light text-body border-bottom">
|
|
<h5 class="text-primary fw-semibold mb-0">
|
|
<i class="bi bi-grid-3x3-gap me-2"></i> Grupos
|
|
</h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div id="lstTabla"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div> <!-- Grilla -->
|
|
|
|
<div class="row"> <!-- Formulario -->
|
|
<div class="col-12">
|
|
<div id="pantallaFormulario" class="card shadow-sm rounded-3 overflow-hidden d-none"
|
|
data-aos="fade-right"
|
|
data-aos-duration="650"
|
|
data-aos-easing="ease-out-cubic"
|
|
data-aos-delay="80"
|
|
data-aos-once="false">
|
|
<div class="card-header bg-light text-body border-bottom">
|
|
<h5 class="text-primary fw-semibold mb-0">
|
|
<i class="bi bi-grid-3x3-gap me-2"></i> Nuevo Grupo
|
|
</h5>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<form id="miFormulario" class="row needs-validation" novalidate> <!-- Formulario -->
|
|
<input type="hidden" id="iptId" name="id">
|
|
|
|
<div class="row"> <!-- ROW: Nombre Activo -->
|
|
|
|
<div class="col-md-6"> <!-- Nombre -->
|
|
<div class="form-group mb-2">
|
|
<label class="col-form-label" for="iptNombre">
|
|
<span class="small">Nombre</span><span class="text-danger">*</span>
|
|
</label>
|
|
<input type="text" class="form-control form-control-sm" id="iptNombre" name="iptNombre"
|
|
placeholder="Ingrese el nombre" autocomplete="off" required>
|
|
<div class="invalid-feedback">Debe ingresar el nombre!</div>
|
|
</div>
|
|
</div> <!-- Nombre -->
|
|
|
|
<div class="col-md-6"> <!-- Activo -->
|
|
<div class="form-group mb-2">
|
|
<label class="col-form-label" for="iptActivo">
|
|
<span class="small">¿Habilitado?</span><span class="text-danger">*</span>
|
|
</label>
|
|
<select class="form-select form-select-sm" id="iptActivo" name="iptActivo" required>
|
|
<option value="">--Seleccione si está habilitado--</option>
|
|
<option value="1">Si</option>
|
|
<option value="0">No</option>
|
|
</select>
|
|
</div>
|
|
</div> <!-- Activo -->
|
|
|
|
</div> <!-- ROW: Nombre Activo -->
|
|
|
|
<div class="row"> <!-- ROW: Descripcion -->
|
|
<div class="col-md-12">
|
|
<div class="form-group mb-2">
|
|
<label class="col-form-label" for="iptDescripcion">
|
|
<span class="small">Descripción</span><span class="text-danger">*</span>
|
|
</label>
|
|
<input type="text" class="form-control form-control-sm" id="iptDescripcion" name="iptDescripcion"
|
|
placeholder="Ingrese el nombre" autocomplete="off" required>
|
|
<div class="invalid-feedback">Debe ingresar la descripción!</div>
|
|
</div>
|
|
</div>
|
|
</div> <!-- ROW: Descripcion -->
|
|
|
|
<div class="row"> <!-- ROW: Integrantes -->
|
|
<div class="col-12 mt-3">
|
|
<label class="col-form-label d-block">
|
|
<span class="small">Miembros del grupo</span>
|
|
</label>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<div id="lbDisponibles"></div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div id="lbMiembros"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<input type="hidden" id="iptMiembros" name="miembros">
|
|
</div>
|
|
</div> <!-- ROW: Integrantes -->
|
|
|
|
<div class="row"> <!-- ROW: Botones -->
|
|
<div class="mt-4">
|
|
<button type="submit" class="btn btn-success me-2" id="btnGuardar">
|
|
<i class="bi bi-save2-fill me-1"></i> Guardar
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" id="btnCancelar">
|
|
<i class="bi bi-arrow-left-short me-1"></i> Cancelar
|
|
</button>
|
|
</div>
|
|
</div> <!-- ROW: Botones -->
|
|
|
|
</form> <!-- Formulario -->
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<?php
|
|
require __DIR__ . '/../partials/footer.php';
|
|
?>
|
|
|
|
<script src="../js/cargar_datos.js"></script>
|
|
<script src="../js/convertir_form.js"></script>
|
|
|
|
<script>
|
|
AOS.init({
|
|
duration: 600, // velocidad “cómoda”
|
|
easing: 'ease-out-cubic',
|
|
once: false,
|
|
offset: 0 // que no espere scroll extra
|
|
});
|
|
|
|
|
|
let lbDisponibles, lbMiembros;
|
|
|
|
|
|
function initDualListBox() {
|
|
lbDisponibles = new ej.dropdowns.ListBox({
|
|
dataSource: [], // se carga luego por AJAX
|
|
fields: {
|
|
text: 'nombre',
|
|
value: 'id'
|
|
},
|
|
allowDragAndDrop: true,
|
|
scope: '#lbMiembros',
|
|
toolbarSettings: {
|
|
items: ['moveTo', 'moveFrom', 'moveAllTo', 'moveAllFrom']
|
|
},
|
|
locale: 'es-ES',
|
|
});
|
|
lbDisponibles.appendTo('#lbDisponibles');
|
|
|
|
lbMiembros = new ej.dropdowns.ListBox({
|
|
dataSource: [],
|
|
fields: {
|
|
text: 'nombre',
|
|
value: 'id'
|
|
},
|
|
allowDragAndDrop: true,
|
|
scope: '#lbDisponibles',
|
|
locale: 'es-ES',
|
|
});
|
|
lbMiembros.appendTo('#lbMiembros');
|
|
|
|
}
|
|
|
|
function cargarUsuariosDisponibles() {
|
|
return $.ajax({
|
|
url: "../ajax/usuarios.ajax.php",
|
|
dataSrc: "",
|
|
data: {
|
|
"accion": "LOV"
|
|
},
|
|
type: "POST",
|
|
dataType: "json"
|
|
}).done(function(respuesta) {
|
|
lbDisponibles.dataSource = respuesta;
|
|
lbDisponibles.refresh();
|
|
});
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
initDualListBox();
|
|
cargarUsuariosDisponibles()
|
|
});
|
|
|
|
// Cargar miembros del grupo si estamos editando (ipasado por iptId)
|
|
function cargarMiembrosDelGrupo(idGrupo) {
|
|
if (!idGrupo) return;
|
|
// url: "",
|
|
return $.getJSON('../ajax/grupos.ajax.php', {
|
|
accion: 'MIEMBROS',
|
|
id: idGrupo
|
|
})
|
|
.then(function(miembros) {
|
|
// Asignar a la lista derecha
|
|
lbMiembros.dataSource = miembros;
|
|
lbMiembros.refresh();
|
|
|
|
// Remover de “Disponibles” los que ya están seleccionados
|
|
const elegidos = new Set(miembros.map(m => String(m.id)));
|
|
const restantes = lbDisponibles.dataSource.filter(u => !elegidos.has(String(u.id)));
|
|
lbDisponibles.dataSource = restantes;
|
|
lbDisponibles.refresh();
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function inicializarFormularioNuevo() {
|
|
$('#miFormulario')[0].reset();
|
|
$('#miFormulario').removeClass('was-validated');
|
|
$('#iptId').val('');
|
|
|
|
$('#pantallaFormulario h5')
|
|
.html('<i class="bi bi-grid-3x3-gap me-2"></i>Nuevo Grupo')
|
|
.removeClass('text-warning')
|
|
.addClass('text-success');
|
|
$('#iptId').hide();
|
|
}
|
|
|
|
document.getElementById("btnCancelar").addEventListener("click", function() {
|
|
cambiarVista('#pantallaGrilla', '#pantallaFormulario');
|
|
});
|
|
|
|
function cargarFormularioParaEditar(rowData) {
|
|
const $form = $('#miFormulario');
|
|
$form[0].reset();
|
|
$form.removeClass('was-validated');
|
|
|
|
var datos = new FormData();
|
|
datos.append("id", rowData.id);
|
|
datos.append("accion", "DATOS");
|
|
$.ajax({
|
|
url: "../ajax/grupos.ajax.php",
|
|
type: "POST",
|
|
data: datos,
|
|
cache: false,
|
|
contentType: false,
|
|
processData: false,
|
|
dataType: 'json',
|
|
success: function(r) {
|
|
if (!r || r.respuesta !== "OK") {
|
|
console.warn("Respuesta no OK:", r);
|
|
return;
|
|
}
|
|
const grupo = Array.isArray(r.grupo) ? (r.grupo[0] || {}) : (r.grupo || {});
|
|
const personasIds = Array.isArray(r.personas) ?
|
|
r.personas.map(p => (p?.id_usuario ?? p)).filter(v => v != null) : [];
|
|
|
|
fillFormFromData($form, grupo);
|
|
actualizarMiembrosGrupo(personasIds);
|
|
|
|
// Ajusta encabezado
|
|
$('#pantallaFormulario h5')
|
|
.html('<i class="bi bi-grid-3x3-gap me-2"></i>Editar Grupo')
|
|
.removeClass('text-success')
|
|
.addClass('text-warning');
|
|
|
|
cambiarVista('#pantallaFormulario', '#pantallaGrilla');
|
|
}
|
|
});
|
|
}
|
|
|
|
function getListBoxValues(listBoxInstance) {
|
|
return Array.from(listBoxInstance.getItems())
|
|
.map(li => li.getAttribute('data-value'))
|
|
.filter(v => v !== null && v !== undefined);
|
|
}
|
|
|
|
function actualizarMiembrosGrupo(ids) {
|
|
const idsSet = new Set(ids.map(String)); // para comparar rápido
|
|
|
|
// dataSource original (donde están todos los usuarios posibles)
|
|
const todos = lbDisponibles.dataSource.concat(lbMiembros.dataSource);
|
|
|
|
// separar disponibles y miembros
|
|
const miembros = todos.filter(u => idsSet.has(String(u.id)));
|
|
const disponibles = todos.filter(u => !idsSet.has(String(u.id)));
|
|
|
|
// reasignar dataSource a cada ListBox
|
|
lbMiembros.dataSource = miembros;
|
|
lbMiembros.refresh();
|
|
|
|
lbDisponibles.dataSource = disponibles;
|
|
lbDisponibles.refresh();
|
|
}
|
|
|
|
function eliminarRegistro(rowData) {
|
|
Swal.fire({
|
|
icon: 'question',
|
|
title: '¿Está seguro?',
|
|
html: `Se eliminará el grupo <b>"${rowData.nombre}"</b>`,
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Sí, borrar',
|
|
cancelButtonText: 'Cancelar',
|
|
buttonsStyling: false, // importante para usar Bootstrap
|
|
customClass: {
|
|
popup: 'my-swal-popup',
|
|
actions: 'my-swal-actions',
|
|
confirmButton: 'btn btn-sm btn-outline-danger', // igual al botón borrar
|
|
cancelButton: 'btn btn-sm btn-outline-primary', // igual al botón editar/cancelar
|
|
icon: 'my-swal-icon'
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
const datos = new FormData();
|
|
datos.append('accion', 'BORRAR');
|
|
datos.append('id', rowData.id);
|
|
|
|
$.ajax({
|
|
url: "../ajax/grupos.ajax.php",
|
|
method: 'POST',
|
|
data: datos,
|
|
cache: false,
|
|
contentType: false,
|
|
processData: false,
|
|
dataType: 'json',
|
|
success: function(respuesta) {
|
|
if (respuesta.respuesta === 'OK') {
|
|
Swal.fire({
|
|
toast: true,
|
|
position: 'top',
|
|
icon: 'success',
|
|
title: 'El registro fue eliminado',
|
|
showConfirmButton: false,
|
|
timer: 2500,
|
|
timerProgressBar: true
|
|
});
|
|
|
|
// Recargar grilla manteniendo estado
|
|
const estadoGrid = grid.getPersistData();
|
|
$.ajax({
|
|
url: "../ajax/grupos.ajax.php",
|
|
method: 'POST',
|
|
data: {
|
|
accion: 'LISTAR'
|
|
},
|
|
dataType: 'json',
|
|
success: function(data) {
|
|
grid.dataSource = data;
|
|
const estado = JSON.parse(estadoGrid);
|
|
if (estado.filterSettings) grid.filterSettings = estado.filterSettings;
|
|
if (estado.searchSettings) grid.searchSettings = estado.searchSettings;
|
|
}
|
|
});
|
|
} else {
|
|
Swal.fire('Error', respuesta.mensaje, 'error');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
$('#lstTabla').on('click', '.btnEliminar', function() {
|
|
const row = $(this).closest('tr');
|
|
const rowData = grid.getRowObjectFromUID(row.attr('data-uid')).data;
|
|
eliminarRegistro(rowData);
|
|
});
|
|
|
|
$('#lstTabla').on('click', '.btnEditar', function() {
|
|
const row = $(this).closest('tr');
|
|
const rowData = grid.getRowObjectFromUID(row.attr('data-uid')).data;
|
|
cargarFormularioParaEditar(rowData);
|
|
});
|
|
|
|
document.getElementById("miFormulario").addEventListener("submit", function(e) {
|
|
e.preventDefault();
|
|
if (!this.checkValidity()) {
|
|
this.classList.add('was-validated');
|
|
return;
|
|
}
|
|
const datos = buildPayload(this);
|
|
// para no salirme del estandar
|
|
datos.delete('lbdisponibles');
|
|
datos.delete('lbmiembros');
|
|
|
|
const grupos = getListBoxValues(lbMiembros);
|
|
datos.append('miembros', JSON.stringify(grupos));
|
|
|
|
$.ajax({
|
|
url: "../ajax/grupos.ajax.php",
|
|
method: "POST",
|
|
data: datos,
|
|
cache: false,
|
|
contentType: false,
|
|
processData: false,
|
|
dataType: "json",
|
|
success: function(resultado) {
|
|
if (resultado.respuesta === "OK") {
|
|
const estadoGrid = grid.getPersistData(); // Guarda el estado
|
|
|
|
Swal.fire({
|
|
position: 'top', // Parte superior, centrado horizontal
|
|
icon: 'success',
|
|
title: 'El registro fue guardado',
|
|
showConfirmButton: false,
|
|
timer: 3000,
|
|
timerProgressBar: true,
|
|
toast: true, // sigue siendo un toast, pero en 'top' se centra
|
|
customClass: {
|
|
popup: 'swal2-toast swal2-top-center'
|
|
}
|
|
});
|
|
cambiarVista('#pantallaGrilla', '#pantallaFormulario');
|
|
|
|
// Recargar grilla manteniendo estado
|
|
$.ajax({
|
|
url: "../ajax/grupos.ajax.php",
|
|
method: 'POST',
|
|
data: {
|
|
accion: 'LISTAR'
|
|
},
|
|
dataType: 'json',
|
|
success: function(data) {
|
|
grid.dataSource = data;
|
|
const estado = JSON.parse(estadoGrid);
|
|
if (estado.filterSettings) grid.filterSettings = estado.filterSettings;
|
|
if (estado.searchSettings) grid.searchSettings = estado.searchSettings;
|
|
}
|
|
});
|
|
} else {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Error al guardar',
|
|
text: resultado.mensaje,
|
|
confirmButtonText: 'Aceptar',
|
|
customClass: {
|
|
confirmButton: 'btn btn-danger',
|
|
popup: 'rounded'
|
|
},
|
|
buttonsStyling: false
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
function cambiarVista(vistaMostrar, vistaOcultar) {
|
|
const $show = $(vistaMostrar).hasClass('card') ? $(vistaMostrar) : $(vistaMostrar).closest('.card');
|
|
const $hide = $(vistaOcultar).hasClass('card') ? $(vistaOcultar) : $(vistaOcultar).closest('.card');
|
|
|
|
// Elegir clase de salida según el efecto de AOS del que se va
|
|
const hideEffect = ($hide.attr('data-aos') || '').toLowerCase();
|
|
const exitClass =
|
|
hideEffect.includes('fade-right') ? 'aos-exit-right' : 'aos-exit-up'; // por defecto "up"
|
|
|
|
const exitMs = parseInt($hide.attr('data-aos-duration'), 10) || 600;
|
|
|
|
// 1) Animación de salida
|
|
$hide.addClass(exitClass).one('animationend', function() {
|
|
// 2) Tras salir, oculto y limpio
|
|
$hide.removeClass(exitClass + ' aos-animate').addClass('d-none');
|
|
|
|
// 3) Preparar y animar la entrada con AOS
|
|
$show.removeClass('d-none aos-animate');
|
|
(AOS.refreshHard || AOS.refresh).call(AOS);
|
|
requestAnimationFrame(() => $show.addClass('aos-animate'));
|
|
});
|
|
|
|
// Fallback por si el evento no dispara (raro, pero seguro)
|
|
setTimeout(() => {
|
|
if (!$hide.hasClass('d-none')) {
|
|
$hide.removeClass(exitClass + ' aos-animate').addClass('d-none');
|
|
$show.removeClass('d-none aos-animate');
|
|
(AOS.refreshHard || AOS.refresh).call(AOS);
|
|
requestAnimationFrame(() => $show.addClass('aos-animate'));
|
|
}
|
|
}, exitMs + 50);
|
|
}
|
|
|
|
let grid;
|
|
$.ajax({ // Solicitud ajax para cargar los usuarios en la grilla
|
|
url: '../ajax/grupos.ajax.php',
|
|
method: 'POST',
|
|
data: {
|
|
accion: 'LISTAR'
|
|
},
|
|
dataType: 'json',
|
|
success: function(data) {
|
|
ej.grids.Grid.Inject(ej.grids.Sort, ej.grids.Search, ej.grids.ColumnChooser, ej.grids.Filter, ej.grids.ExcelExport);
|
|
grid = new ej.grids.Grid({
|
|
id: 'lstTabla',
|
|
dataSource: data,
|
|
locale: 'es-ES',
|
|
allowPaging: true,
|
|
allowSorting: true,
|
|
allowFiltering: true,
|
|
allowExcelExport: true,
|
|
showColumnChooser: true,
|
|
filterSettings: {
|
|
type: 'Excel',
|
|
operators: {
|
|
stringOperator: [{
|
|
value: 'contains',
|
|
text: 'Contiene'
|
|
},
|
|
{
|
|
value: 'equal',
|
|
text: 'Igual'
|
|
},
|
|
{
|
|
value: 'startswith',
|
|
text: 'Empieza con'
|
|
},
|
|
{
|
|
value: 'endswith',
|
|
text: 'Termina con'
|
|
}
|
|
]
|
|
}
|
|
},
|
|
toolbar: [
|
|
'Search',
|
|
{
|
|
text: '<i class="bi bi-layout-three-columns"></i> Columnas',
|
|
tooltipText: 'Mostrar/ocultar columnas',
|
|
id: 'btnColumnas'
|
|
},
|
|
{
|
|
text: '<i class="bi bi-person-fill-add"></i> Nuevo',
|
|
tooltipText: 'Crear nuevo grupo',
|
|
id: 'btnNuevo'
|
|
},
|
|
{
|
|
text: '<i class="bi bi-file-earmark-excel-fill"></i> Excel',
|
|
tooltipText: 'Exportar a Excel',
|
|
id: 'btnExcel'
|
|
}
|
|
],
|
|
toolbarClick: function(args) {
|
|
if (args.item.id === 'btnExcel') {
|
|
const hoy = new Date().toISOString().slice(0, 10);
|
|
grid.excelExport({
|
|
fileName: `grupos_${hoy}.xlsx`
|
|
});
|
|
} else if (args.item.id === 'btnNuevo') {
|
|
inicializarFormularioNuevo();
|
|
cambiarVista('#pantallaFormulario', '#pantallaGrilla');
|
|
} else if (args.item.id === 'btnColumnas') {
|
|
grid.columnChooserModule.openColumnChooser(0, 0); // puedes ajustar posición x, y
|
|
}
|
|
},
|
|
disableHtmlEncode: true,
|
|
pageSettings: {
|
|
pageSize: 10
|
|
},
|
|
locale: 'es-ES',
|
|
columns: [{
|
|
headerText: '',
|
|
width: 100,
|
|
template: function() {
|
|
return `
|
|
<div class="text-center d-flex justify-content-center gap-2">
|
|
<button class="btn btn-sm btn-outline-primary btn-icon-sm btnEditar" title="Editar">
|
|
<i class="bi bi-pencil-fill"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger btn-icon-sm btnEliminar" title="Eliminar">
|
|
<i class="bi bi-trash-fill"></i>
|
|
</button>
|
|
</div>
|
|
`;
|
|
},
|
|
allowSorting: false
|
|
},
|
|
{
|
|
field: 'id',
|
|
headerText: '#',
|
|
width: 50
|
|
},
|
|
{
|
|
field: 'nombre',
|
|
headerText: 'Nombre',
|
|
width: 100,
|
|
clipMode: 'EllipsisWithTooltip'
|
|
},
|
|
{
|
|
field: 'activo',
|
|
headerText: '¿Activo?',
|
|
visible: false,
|
|
width: 50
|
|
},
|
|
{
|
|
field: 'integrantes',
|
|
headerText: 'Integrantes',
|
|
width: 160,
|
|
clipMode: 'EllipsisWithTooltip'
|
|
}
|
|
],
|
|
});
|
|
grid.locale = 'es-ES';
|
|
grid.appendTo('#lstTabla');
|
|
|
|
$('#lstTabla').on('click', '.btnEditar', function() {
|
|
const row = $(this).closest('tr');
|
|
const rowData = grid.getRowObjectFromUID(row.attr('data-uid')).data;
|
|
|
|
// cargarFormularioParaEditar(rowData);
|
|
});
|
|
|
|
$('#lstTabla').on('click', '.btnEliminar', function() {
|
|
const row = $(this).closest('tr');
|
|
const rowData = grid.getRowObjectFromUID(row.attr('data-uid')).data;
|
|
});
|
|
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('Error cargando datos:', error);
|
|
}
|
|
});
|
|
</script>
|