mirror of
https://git.imnavajas.es/jjimenez/safekat.git
synced 2025-07-25 22:52:08 +00:00
Trabajando JS
This commit is contained in:
@ -9,7 +9,7 @@ $routes->group('importador', ['namespace' => 'App\Controllers\Importadores'], fu
|
|||||||
/* Libros */
|
/* Libros */
|
||||||
$routes->group('catalogo', ['namespace' => 'App\Controllers\Importadores'], function ($routes) {
|
$routes->group('catalogo', ['namespace' => 'App\Controllers\Importadores'], function ($routes) {
|
||||||
/**======================
|
/**======================
|
||||||
* CRUD
|
* Tool
|
||||||
*========================**/
|
*========================**/
|
||||||
$routes->get('', 'ImportadorCatalogo::index', ['as' => 'importadorCatalogoTool']);
|
$routes->get('', 'ImportadorCatalogo::index', ['as' => 'importadorCatalogoTool']);
|
||||||
|
|
||||||
@ -17,6 +17,7 @@ $routes->group('importador', ['namespace' => 'App\Controllers\Importadores'], fu
|
|||||||
/**======================
|
/**======================
|
||||||
* AJAX
|
* AJAX
|
||||||
*========================**/
|
*========================**/
|
||||||
|
$routes->post('validar-fila', 'ImportadorCatalogo::validarFila');
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -185,4 +185,53 @@ class ImportadorCatalogo extends BaseResourceController
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function validarFila()
|
||||||
|
{
|
||||||
|
$json = $this->request->getJSON();
|
||||||
|
|
||||||
|
if (!$json || !isset($json->fila[0])) {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'apto' => false,
|
||||||
|
'reason' => 'Datos inválidos'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = trim($json->fila[0]); // Asumimos que 'input' es el primer campo de la fila
|
||||||
|
|
||||||
|
if (empty($input)) {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'apto' => false,
|
||||||
|
'reason' => 'ISBN no proporiconado'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$catalogoModel = new CatalogoLibroModel();
|
||||||
|
|
||||||
|
// 1. Buscar por ISBN exacto
|
||||||
|
$libroPorIsbn = $catalogoModel->where('isbn', $input)->first();
|
||||||
|
|
||||||
|
if ($libroPorIsbn) {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'apto' => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Buscar por EAN sin guiones
|
||||||
|
$eanLimpio = str_replace('-', '', $input);
|
||||||
|
|
||||||
|
$libroPorEan = $catalogoModel->where('REPLACE(ean, "-", "")', $eanLimpio)->first();
|
||||||
|
|
||||||
|
if ($libroPorEan) {
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'apto' => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No encontrado
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'apto' => false,
|
||||||
|
'reason' => 'No encontrado en catálogo'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ return [
|
|||||||
'idlinea' => 'Ref. cliente',
|
'idlinea' => 'Ref. cliente',
|
||||||
'cnt_pedida' => 'Unidades',
|
'cnt_pedida' => 'Unidades',
|
||||||
'precio_compra' => 'Precio Compra',
|
'precio_compra' => 'Precio Compra',
|
||||||
|
'importar' => 'Importar',
|
||||||
|
'subirArchivo' => 'Cargar Excel proporcionado por RA-MA',
|
||||||
|
|
||||||
'libro' => 'libro',
|
'libro' => 'libro',
|
||||||
'id' => 'ID',
|
'id' => 'ID',
|
||||||
|
|||||||
@ -10,47 +10,79 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><?= lang('Importador.importadorCatalogoTitle') ?></h3>
|
<h3 class="card-title"><?= lang('Importador.importadorCatalogoTitle') ?></h3>
|
||||||
</div><!--//.card-header -->
|
</div><!--//.card-header -->
|
||||||
<div class="card-body">
|
|
||||||
<?= view('themes/_commonPartialsBs/_alertBoxes'); ?>
|
|
||||||
|
|
||||||
<input type="file" id="excelFile" accept=".xlsx, .xls">
|
<form id="catalogoLibroForm" class="card-body" method="post" action="#">
|
||||||
<div id="tableContainer"></div>
|
<?= csrf_field() ?>
|
||||||
|
|
||||||
<br>
|
<!-- card-body -->
|
||||||
<button id="importBtn">Importar</button>
|
<div class="card-body">
|
||||||
|
|
||||||
<table id="excelTable" class="table table-striped table-hover" style="width: 100%;">
|
<?= view('themes/_commonPartialsBs/_alertBoxes'); ?>
|
||||||
<thead>
|
<div class="row">
|
||||||
<tr>
|
|
||||||
<th><?= lang('Importador.input') ?></th>
|
|
||||||
<th><?= lang('Importador.idlinea') ?></th>
|
|
||||||
<th><?= lang('Importador.descripcion') ?></th>
|
|
||||||
<th><?= lang('Importador.cnt_pedida') ?></th>
|
|
||||||
<th><?= lang('Importador.precio_compra') ?></th>
|
|
||||||
<th class="text-nowrap" style="min-width: 85px;"><?= lang('Basic.global.Action') ?></th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th></th>
|
|
||||||
<th></th>
|
|
||||||
<th></th>
|
|
||||||
<th></th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
|
|
||||||
</tbody>
|
<div class="col-md-6 mb-3">
|
||||||
</table>
|
<label for="excelFile"
|
||||||
|
class="form-label"><?= lang('Importador.subirArchivo') ?? 'Subir archivo Excel' ?></label>
|
||||||
|
<input type="file" class="form-control" id="excelFile" name="excelFile"
|
||||||
|
accept=".xlsx, .xls">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4 mb-3 d-flex align-items-end">
|
||||||
|
<button type="button" id="importBtn" class="btn btn-success w-100">
|
||||||
|
<i class="fas fa-file-import me-2"></i> <?= lang('Importador.importar') ?? 'Importar' ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12 mb-3">
|
||||||
|
|
||||||
</div><!--//.card-body -->
|
<div class="table-responsive">
|
||||||
<div class="card-footer">
|
<table id="excelTable" class="table table-striped table-hover" style="width: 100%;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<input type="checkbox" id="select-all" class="form-check-input">
|
||||||
|
</th>
|
||||||
|
<th><?= lang('Importador.input') ?></th>
|
||||||
|
<th><?= lang('Importador.idlinea') ?></th>
|
||||||
|
<th><?= lang('Importador.descripcion') ?></th>
|
||||||
|
<th><?= lang('Importador.cnt_pedida') ?></th>
|
||||||
|
<th><?= lang('Importador.precio_compra') ?></th>
|
||||||
|
<th class="text-nowrap" style="min-width: 120px;">
|
||||||
|
<?= lang('Basic.global.Action') ?></th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th><input type="text" class="form-control form-control-sm"
|
||||||
|
placeholder="Filtrar..." /></th>
|
||||||
|
<th><input type="text" class="form-control form-control-sm"
|
||||||
|
placeholder="Filtrar..." /></th>
|
||||||
|
<th><input type="text" class="form-control form-control-sm"
|
||||||
|
placeholder="Filtrar..." /></th>
|
||||||
|
<th><input type="text" class="form-control form-control-sm"
|
||||||
|
placeholder="Filtrar..." /></th>
|
||||||
|
<th><input type="text" class="form-control form-control-sm"
|
||||||
|
placeholder="Filtrar..." /></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div><!--//.card-footer -->
|
</div>
|
||||||
</div><!--//.card -->
|
|
||||||
</div><!--//.col -->
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div><!--//.card-body -->
|
||||||
|
<div class="card-footer">
|
||||||
|
|
||||||
|
</div><!--//.card-footer -->
|
||||||
|
</div><!--//.card -->
|
||||||
|
</div><!--//.col -->
|
||||||
</div><!--//.row -->
|
</div><!--//.row -->
|
||||||
|
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|||||||
@ -2,13 +2,21 @@ import Ajax from '../../../components/ajax.js';
|
|||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
|
||||||
// Columnas que espera la tabla (en el orden de HTML)
|
|
||||||
const TABLE_COLUMNS = ["input", "idlinea", "descripcion", "cnt_pedida", "precio_compra"];
|
const TABLE_COLUMNS = ["input", "idlinea", "descripcion", "cnt_pedida", "precio_compra"];
|
||||||
let dataTable; // referencia al DataTable
|
let dataTable;
|
||||||
|
|
||||||
dataTable = $('#excelTable').DataTable({
|
dataTable = $('#excelTable').DataTable({
|
||||||
orderCellsTop: true,
|
orderCellsTop: true,
|
||||||
fixedHeader: true
|
responsive: true,
|
||||||
|
scrollX: true,
|
||||||
|
lengthMenu: [5, 10, 25, 50, 75, 100, 250, 500, 1000, 2500],
|
||||||
|
pageLength: 25,
|
||||||
|
lengthChange: true,
|
||||||
|
dom: 'lfrtip',
|
||||||
|
language: {
|
||||||
|
url: "/themes/vuexy/vendor/libs/datatables-sk/plugins/i18n/es-ES.json"
|
||||||
|
},
|
||||||
|
order: [[1, 'asc']]
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('excelFile').addEventListener('change', function (e) {
|
document.getElementById('excelFile').addEventListener('change', function (e) {
|
||||||
@ -28,124 +36,156 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
function validateAndLoadDataTable(data) {
|
async function validateAndLoadDataTable(data) {
|
||||||
if (data.length === 0) return;
|
if (data.length === 0) return;
|
||||||
|
|
||||||
const headers = data[0].map(h => h.toString().trim());
|
const headers = data[0].map(h => h.toString().trim());
|
||||||
|
|
||||||
// Crear un índice rápido de nombreColumna => posicion
|
|
||||||
const headerMap = {};
|
const headerMap = {};
|
||||||
headers.forEach((name, idx) => {
|
headers.forEach((name, idx) => {
|
||||||
headerMap[name.toLowerCase()] = idx; // pasar todo a minúsculas para evitar errores
|
headerMap[name.toLowerCase()] = idx;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verificar si todas las columnas requeridas existen
|
|
||||||
const missing = TABLE_COLUMNS.filter(col => !(col in headerMap));
|
const missing = TABLE_COLUMNS.filter(col => !(col in headerMap));
|
||||||
if (missing.length > 0) {
|
if (missing.length > 0) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
text: 'Faltan las siguientes columnas en el Excel: ' + missing.join(', '),
|
text: 'Faltan las siguientes columnas: ' + missing.join(', '),
|
||||||
icon: 'error',
|
icon: 'error',
|
||||||
confirmButtonText: 'Aceptar',
|
confirmButtonText: 'Aceptar',
|
||||||
buttonsStyling: true,
|
buttonsStyling: true,
|
||||||
customClass: {
|
customClass: { confirmButton: 'btn btn-danger' }
|
||||||
confirmButton: 'btn btn-danger'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
dataTable.clear().draw(); // limpia tabla
|
dataTable.clear().draw();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = [];
|
const rows = [];
|
||||||
for (let i = 1; i < data.length; i++) {
|
for (let i = 1; i < data.length; i++) {
|
||||||
const row = [];
|
const rowData = TABLE_COLUMNS.map(col => data[i][headerMap[col]] ?? '');
|
||||||
|
|
||||||
TABLE_COLUMNS.forEach(col => {
|
// Llamar backend para validar la fila
|
||||||
const idx = headerMap[col];
|
const isValid = await validarFila(rowData);
|
||||||
row.push(data[i][idx] ?? '');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Agregar botón al final
|
let checkboxHtml = '';
|
||||||
row.push('<button type="button" class="btn btn-danger btn-sm deleteRow">Eliminar</button>');
|
let actionBtnsHtml = '';
|
||||||
|
|
||||||
rows.push(row);
|
if (isValid) {
|
||||||
|
checkboxHtml = `<input type="checkbox" class="select-row form-check-input" checked>`;
|
||||||
|
|
||||||
|
actionBtnsHtml = `
|
||||||
|
<div class="d-flex flex-column align-items-start">
|
||||||
|
<button type="button" class="btn btn-outline-success btn-sm mb-1 importRow">
|
||||||
|
<i class="fas fa-file-import me-1"></i> Importar
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-danger btn-sm deleteRow">
|
||||||
|
<i class="fas fa-trash-alt me-1"></i> Eliminar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
checkboxHtml = `<input type="checkbox" class="select-row form-check-input" disabled>`;
|
||||||
|
|
||||||
|
actionBtnsHtml = `
|
||||||
|
<div class="d-flex flex-column align-items-start">
|
||||||
|
<span class="badge rounded-pill bg-danger mb-2">No Apto</span>
|
||||||
|
<button type="button" class="btn btn-outline-danger btn-sm deleteRow">
|
||||||
|
<i class="fas fa-trash-alt me-1"></i> Eliminar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.push([checkboxHtml, ...rowData, actionBtnsHtml]);
|
||||||
}
|
}
|
||||||
|
|
||||||
dataTable.clear().rows.add(rows).draw();
|
dataTable.clear().rows.add(rows).draw();
|
||||||
|
|
||||||
// Agregar eventos dinámicos para eliminar
|
setupEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validarFila(rowData) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/importador/catalogo/validar-fila', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '<?= csrf_hash() ?>'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ fila: rowData })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
return result.apto === true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error validando fila', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupEventListeners() {
|
||||||
$('#excelTable tbody').off('click', '.deleteRow').on('click', '.deleteRow', function () {
|
$('#excelTable tbody').off('click', '.deleteRow').on('click', '.deleteRow', function () {
|
||||||
dataTable.row($(this).parents('tr')).remove().draw();
|
dataTable.row($(this).parents('tr')).remove().draw();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#excelTable tbody').off('click', '.importRow').on('click', '.importRow', function () {
|
||||||
|
const rowData = dataTable.row($(this).parents('tr')).data();
|
||||||
|
console.log('Importar esta fila:', rowData);
|
||||||
|
// Aquí podrías enviar sólo esta fila al servidor si quieres importar individualmente
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#excelTable thead tr:eq(1) th').each(function (i) {
|
|
||||||
const title = $(this).text();
|
|
||||||
|
|
||||||
if (title.trim() !== '') { // Solo si el th tiene título
|
|
||||||
$(this).html('<input type="text" class="form-control form-control-sm" placeholder="Filtrar..." />');
|
|
||||||
|
|
||||||
$('input', this).on('keyup change', function () {
|
|
||||||
if (dataTable.column(i).search() !== this.value) {
|
|
||||||
dataTable
|
|
||||||
.column(i)
|
|
||||||
.search(this.value)
|
|
||||||
.draw();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('importBtn').addEventListener('click', function () {
|
document.getElementById('importBtn').addEventListener('click', function () {
|
||||||
const allData = dataTable.rows().data().toArray();
|
const selectedRows = [];
|
||||||
const rowsToSend = allData.map(row => row.slice(0, -1)); // sin botón
|
|
||||||
|
dataTable.rows().every(function () {
|
||||||
if (rowsToSend.length === 0) {
|
const data = this.data();
|
||||||
|
const checkboxHtml = $(data[0]).find('input.select-row');
|
||||||
|
if (checkboxHtml.length > 0 && checkboxHtml.is(':checked') && !checkboxHtml.is(':disabled')) {
|
||||||
|
selectedRows.push(data.slice(1, -1)); // sin checkbox ni botones
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedRows.length === 0) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: 'Atención',
|
title: 'Atención',
|
||||||
text: 'No hay datos para importar.',
|
text: 'No hay filas aptas seleccionadas para importar.',
|
||||||
icon: 'warning',
|
icon: 'warning',
|
||||||
confirmButtonText: 'Aceptar',
|
confirmButtonText: 'Aceptar',
|
||||||
buttonsStyling: true,
|
buttonsStyling: true,
|
||||||
customClass: {
|
customClass: { confirmButton: 'btn btn-warning' }
|
||||||
confirmButton: 'btn btn-warning'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('/importar', {
|
fetch('/importar', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-TOKEN': '<?= csrf_hash() ?>'
|
'X-CSRF-TOKEN': '<?= csrf_hash() ?>'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ data: rowsToSend })
|
body: JSON.stringify({ data: selectedRows })
|
||||||
}).then(res => res.json())
|
}).then(res => res.json())
|
||||||
.then(response => {
|
.then(response => {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: 'Importación exitosa',
|
title: 'Importación exitosa',
|
||||||
text: response.message,
|
text: response.message,
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
confirmButtonText: 'Aceptar',
|
confirmButtonText: 'Aceptar',
|
||||||
buttonsStyling: true,
|
buttonsStyling: true,
|
||||||
customClass: {
|
customClass: { confirmButton: 'btn btn-success' }
|
||||||
confirmButton: 'btn btn-success'
|
});
|
||||||
}
|
})
|
||||||
});
|
.catch(error => {
|
||||||
})
|
console.error(error);
|
||||||
.catch(error => {
|
Swal.fire({
|
||||||
console.error(error);
|
title: 'Error',
|
||||||
Swal.fire({
|
text: 'Error importando datos.',
|
||||||
title: 'Error',
|
icon: 'error',
|
||||||
text: 'Hubo un problema al importar los datos.',
|
confirmButtonText: 'Aceptar',
|
||||||
icon: 'error',
|
buttonsStyling: true,
|
||||||
confirmButtonText: 'Aceptar',
|
customClass: { confirmButton: 'btn btn-danger' }
|
||||||
buttonsStyling: true,
|
});
|
||||||
customClass: {
|
});
|
||||||
confirmButton: 'btn btn-danger'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user