mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-24 09:40:21 +00:00
direcciones terminadas
This commit is contained in:
@ -0,0 +1,140 @@
|
||||
// Requiere jQuery y Bootstrap 5 (para los data-bs-* de los modals)
|
||||
export class DireccionCard {
|
||||
/**
|
||||
* @param {Object} opts
|
||||
* @param {Object} opts.direccion // objeto con tus campos de BBDD
|
||||
* - id, user_id, alias, att, direccion, cp, ciudad, provincia, pais_code3,
|
||||
* telefono, is_facturacion, razon_social,
|
||||
* tipo_identificacion_fiscal, identificacion_fiscal
|
||||
*/
|
||||
constructor(opts) {
|
||||
this.opts = Object.assign({}, opts || {});
|
||||
|
||||
this.dir = this.opts.direccion || {};
|
||||
this.id = `shippingAddress_${this.dir.id || Math.random().toString(36).slice(2, 8)}`;
|
||||
this.$el = null;
|
||||
}
|
||||
|
||||
// Escapa HTML para evitar XSS si llega texto “sucio”.
|
||||
_esc(str) {
|
||||
return String(str ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
// Construye la parte “línea de dirección”
|
||||
_buildAddressLine() {
|
||||
const p = this.dir;
|
||||
const partes = [
|
||||
this._esc(p.direccion),
|
||||
[this._esc(p.cp), this._esc(p.ciudad)].filter(Boolean).join(" "),
|
||||
[this._esc(p.provincia), this._esc((p.pais_code3 || "").toUpperCase())].filter(Boolean).join(", ")
|
||||
].filter(Boolean);
|
||||
return partes.join(", ");
|
||||
}
|
||||
|
||||
toElement() {
|
||||
if (this.$el) return this.$el;
|
||||
|
||||
const d = this.dir;
|
||||
const isFact = !!d.is_facturacion || !!d.isFacturacion;
|
||||
|
||||
const header = this._esc(d.alias || "Dirección");
|
||||
const contactLine = this._esc(d.att || "");
|
||||
const razon_social = this._esc(d.razon_social || "");
|
||||
const country = (d.pais || "").toUpperCase();
|
||||
const addressLine = this._buildAddressLine();
|
||||
const phoneLine = this._esc(d.telefono || "");
|
||||
const extraFiscal = isFact
|
||||
? `<span class="text-muted mt-2 fw-normal d-block">
|
||||
<i class="ri-check-line"></i> ${window.languageBundle.get(['direcciones.isFacturacionShort']) || 'Disponible para facturación'}<br/>
|
||||
${razon_social ? razon_social + "<br/>" : ""}
|
||||
${this._esc(d.tipo_identificacion_fiscal || "")}${(d.tipo_identificacion_fiscal && d.identificacion_fiscal) ? ": " : ""}
|
||||
${this._esc(d.identificacion_fiscal || "")}
|
||||
</span>`
|
||||
: "";
|
||||
|
||||
const html = `
|
||||
<div class="col-lg-4 col-sm-6 ">
|
||||
<div class="form-check card h-100 px-0">
|
||||
<input
|
||||
id="${this._esc(this.id)}"
|
||||
type="hidden"
|
||||
class="form-check-input"
|
||||
data-id="${this._esc(d.id ?? '')}">
|
||||
<label class="form-check-label h-100 d-flex flex-column" for="${this._esc(this.id)}">
|
||||
<div class="p-2 mx-3">
|
||||
<span class="mb-2 fw-semibold d-block text-muted text-uppercase">${header}</span>
|
||||
${contactLine ? `<span class="fs-14 mb-1 d-block">${contactLine}</span>` : ''}
|
||||
${addressLine ? `<span class="text-muted fw-normal text-wrap mb-1 d-block">${addressLine}</span>` : ''}
|
||||
${country ? `<span class="text-muted fw-normal d-block">${country}</span>` : ''}
|
||||
${phoneLine ? `<span class="text-muted fw-normal d-block">${window.languageBundle.get(['direcciones.telefono'])}: ${phoneLine}</span>` : ''}
|
||||
${extraFiscal}
|
||||
</div>
|
||||
|
||||
<!-- Acciones integradas en la tarjeta -->
|
||||
<div class="d-flex flex-wrap align-items-center gap-2 px-2 py-1 bg-light rounded-bottom border-top mt-auto actions-row">
|
||||
<a href="#" class="d-block text-body p-1 px-2 btn-edit-direccion" data-id="${this._esc(d.id ?? '')}">
|
||||
<i class="ri-pencil-fill text-muted align-bottom me-1"></i> ${window.languageBundle.get(['direcciones.btn.edit']) || 'Editar'}
|
||||
</a>
|
||||
<a href="#" class="d-block text-body p-1 px-2 btn-delete-direccion" data-id="${this._esc(d.id ?? '')}">
|
||||
<i class="ri-delete-bin-fill text-muted align-bottom me-1"></i> ${window.languageBundle.get(['direcciones.btn.delete']) || 'Eliminar'}
|
||||
</a>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.$el = $(html);
|
||||
|
||||
return this.$el;
|
||||
}
|
||||
|
||||
appendTo($container) {
|
||||
const $node = this.toElement();
|
||||
$container.append($node);
|
||||
return this;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.dir.id ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ===================== Ejemplo de uso =====================
|
||||
|
||||
const direccion = {
|
||||
id: 123,
|
||||
user_id: 45,
|
||||
alias: "Casa",
|
||||
att: "Juan Pérez",
|
||||
direccion: "C/ Hola 22",
|
||||
cp: "28001",
|
||||
ciudad: "Madrid",
|
||||
provincia: "Madrid",
|
||||
pais_code3: "ESP",
|
||||
telefono: "600123123",
|
||||
instrucciones: "Llamar al timbre 2ºB",
|
||||
is_facturacion: true,
|
||||
razon_social: "Editorial ImprimeLibros S.L.",
|
||||
tipo_identificacion_fiscal: "CIF",
|
||||
identificacion_fiscal: "B12345678"
|
||||
};
|
||||
|
||||
new DireccionCard({
|
||||
direccion,
|
||||
name: "direccionSeleccionada",
|
||||
checked: true,
|
||||
editModal: "#direccionEditarModal",
|
||||
removeModal: "#direccionEliminarModal",
|
||||
onEdit: (dir) => { console.log("Editar", dir); },
|
||||
onRemove: (dir) => { console.log("Eliminar", dir); },
|
||||
onChange: (dir, checked) => { if (checked) console.log("Seleccionada", dir.id); }
|
||||
}).appendTo($("#direccionesGrid"));
|
||||
|
||||
=========================================================== */
|
||||
@ -126,7 +126,6 @@
|
||||
$(document).on('click', '.btn-edit-direccion', function (e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
e.preventDefault();
|
||||
$.get('/direcciones/form', { id }, function (html) {
|
||||
$('#direccionFormModalBody').html(html);
|
||||
const title = $('#direccionFormModalBody #direccionForm').data('edit');
|
||||
@ -169,7 +168,7 @@
|
||||
confirmButton: 'btn btn-secondary w-xs mt-2',
|
||||
},
|
||||
});
|
||||
$('#margenes-datatable').DataTable().ajax.reload(null, false);
|
||||
$('#direccion-datatable').DataTable().ajax.reload(null, false);
|
||||
},
|
||||
error: function (xhr) {
|
||||
// usa el mensaje del backend; fallback genérico por si no llega JSON
|
||||
|
||||
@ -0,0 +1,219 @@
|
||||
|
||||
|
||||
import { DireccionCard } from './direccionCard.js';
|
||||
(() => {
|
||||
// si jQuery está cargado, añade CSRF a AJAX
|
||||
const csrfToken = document.querySelector('meta[name="_csrf"]')?.getAttribute('content');
|
||||
const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.getAttribute('content');
|
||||
if (window.$ && csrfToken && csrfHeader) {
|
||||
$.ajaxSetup({
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader(csrfHeader, csrfToken);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const language = document.documentElement.lang || 'es-ES';
|
||||
|
||||
// Comprueba dependencias antes de iniciar
|
||||
if (!window.DataTable) {
|
||||
console.error('DataTables no está cargado aún');
|
||||
return;
|
||||
}
|
||||
|
||||
const $container = $('#direccionesContainer');
|
||||
const $shadow = $('#dtDirecciones');
|
||||
|
||||
// Inicializa DataTable en modo server-side
|
||||
const dt = $shadow.DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
deferRender: true,
|
||||
pageLength: 10, // sincronizado con el select
|
||||
lengthChange: false, // manejado por #pageSize
|
||||
searching: true,
|
||||
ordering: true,
|
||||
paging: true,
|
||||
order: [[0, 'asc']], // orden inicial por alias
|
||||
dom: 'tpi',
|
||||
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
|
||||
ajax: {
|
||||
url: '/direcciones/datatableDirecciones', // ajusta a tu endpoint
|
||||
type: 'GET',
|
||||
// Si tu backend espera JSON puro, descomenta:
|
||||
// contentType: 'application/json',
|
||||
// data: function (d) { return JSON.stringify(d); }
|
||||
// Si espera form-urlencoded (el típico de DataTables), deja como está.
|
||||
data: function (d) {
|
||||
// Puedes incluir filtros extra aquí si los necesitas
|
||||
// d.isFacturacionOnly = $('#chkFacturacion').prop('checked') ? 1 : 0;
|
||||
}
|
||||
},
|
||||
// Mapea las columnas a las propiedades del JSON que retorna tu backend
|
||||
columns: [
|
||||
{ data: 'alias', name: 'alias' },
|
||||
{ data: 'att', name: 'att' },
|
||||
{ data: 'direccion', name: 'direccion' },
|
||||
{ data: 'cp', name: 'cp' },
|
||||
{ data: 'ciudad', name: 'ciudad' },
|
||||
{ data: 'provincia', name: 'provincia' },
|
||||
{ data: 'pais', name: 'pais' },
|
||||
{ data: 'telefono', name: 'telefono' },
|
||||
{ data: 'is_facturacion', name: 'is_facturacion' },
|
||||
{ data: 'razon_social', name: 'razon_social' },
|
||||
{ data: 'tipo_identificacion_fiscal', name: 'tipo_identificacion_fiscal' },
|
||||
{ data: 'identificacion_fiscal', name: 'identificacion_fiscal' },
|
||||
{ data: 'id', name: 'id' }
|
||||
],
|
||||
// No usamos rows/tds visibles; renderizamos tarjetas en drawCallback
|
||||
drawCallback: function () {
|
||||
const api = this.api();
|
||||
const $container = $('#direccionesContainer').empty();
|
||||
|
||||
api.rows().every(function () {
|
||||
const dir = this.data();
|
||||
try {
|
||||
new DireccionCard({
|
||||
direccion: dir,
|
||||
}).appendTo($container);
|
||||
} catch (err) {
|
||||
console.error('Error renderizando tarjeta de dirección', dir, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Buscar
|
||||
$('#buscadorDirecciones').on('keyup', function () {
|
||||
dt.search(this.value).draw();
|
||||
});
|
||||
|
||||
// Page size
|
||||
$('#pageSize').on('change', function () {
|
||||
dt.page.len(parseInt(this.value, 10)).draw();
|
||||
});
|
||||
|
||||
|
||||
const modalEl = document.getElementById('direccionFormModal');
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
|
||||
$(document).on("change", ".direccionFacturacion", function () {
|
||||
const isChecked = $(this).is(':checked');
|
||||
if (isChecked) {
|
||||
$('.direccionFacturacionItems').removeClass('d-none');
|
||||
} else {
|
||||
$('.direccionFacturacionItems').addClass('d-none');
|
||||
$('#razonSocial').val('');
|
||||
$('#tipoIdentificacionFiscal').val('DNI');
|
||||
$('#identificacionFiscal').val('');
|
||||
}
|
||||
});
|
||||
|
||||
// Abrir "Crear"
|
||||
$('#addButton').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
$.get('/direcciones/form', function (html) {
|
||||
$('#direccionFormModalBody').html(html);
|
||||
const title = $('#direccionFormModalBody #direccionForm').data('add');
|
||||
$('#direccionFormModal .modal-title').text(title);
|
||||
modal.show();
|
||||
});
|
||||
});
|
||||
|
||||
// Abrir "Editar"
|
||||
$(document).on('click', '.btn-edit-direccion', function (e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
$.get('/direcciones/form', { id }, function (html) {
|
||||
$('#direccionFormModalBody').html(html);
|
||||
const title = $('#direccionFormModalBody #direccionForm').data('edit');
|
||||
$('#direccionFormModal .modal-title').text(title);
|
||||
modal.show();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Botón "Eliminar"
|
||||
$(document).on('click', '.btn-delete-direccion', function (e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
|
||||
Swal.fire({
|
||||
title: window.languageBundle.get(['direcciones.delete.title']) || 'Eliminar dirección',
|
||||
html: window.languageBundle.get(['direcciones.delete.text']) || 'Esta acción no se puede deshacer.',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
buttonsStyling: false,
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-danger w-xs mt-2',
|
||||
cancelButton: 'btn btn-light w-xs mt-2'
|
||||
},
|
||||
confirmButtonText: window.languageBundle.get(['direcciones.delete.button']) || 'Eliminar',
|
||||
cancelButtonText: window.languageBundle.get(['app.cancelar']) || 'Cancelar',
|
||||
}).then((result) => {
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
$.ajax({
|
||||
url: '/direcciones/' + id,
|
||||
type: 'DELETE',
|
||||
success: function () {
|
||||
Swal.fire({
|
||||
icon: 'success', title: window.languageBundle.get(['direcciones.delete.ok.title']) || 'Eliminado',
|
||||
text: window.languageBundle.get(['direcciones.delete.ok.text']) || 'La dirección ha sido eliminada con éxito.',
|
||||
showConfirmButton: true,
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-secondary w-xs mt-2',
|
||||
},
|
||||
});
|
||||
$('#dtDirecciones').DataTable().ajax.reload(null, false);
|
||||
},
|
||||
error: function (xhr) {
|
||||
// usa el mensaje del backend; fallback genérico por si no llega JSON
|
||||
const msg = (xhr.responseJSON && xhr.responseJSON.message)
|
||||
|| 'Error al eliminar la direccion.';
|
||||
Swal.fire({ icon: 'error', title: 'No se pudo eliminar', text: msg });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Submit del form en el modal
|
||||
$(document).on('submit', '#direccionForm', 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="direccionForm"') !== -1 && html.indexOf('<html') === -1) {
|
||||
$('#direccionFormModalBody').html(html);
|
||||
const isEdit = $('#direccionFormModalBody #direccionForm input[name="_method"][value="PUT"]').length > 0;
|
||||
const title = $('#direccionFormModalBody #direccionForm').data(isEdit ? 'edit' : 'add');
|
||||
$('#direccionModal .modal-title').text(title);
|
||||
return;
|
||||
}
|
||||
// Éxito real: cerrar y recargar tabla
|
||||
modal.hide();
|
||||
dt.ajax.reload(null, false);
|
||||
},
|
||||
error: function (xhr) {
|
||||
// Con 422 devolvemos el fragmento con errores aquí
|
||||
if (xhr.status === 422 && xhr.responseText) {
|
||||
$('#direccionFormModalBody').html(xhr.responseText);
|
||||
const isEdit = $('#direccionFormModalBody #direccionForm input[name="_method"][value="PUT"]').length > 0;
|
||||
const title = $('#direccionFormModalBody #direccionForm').data(isEdit ? 'edit' : 'add');
|
||||
$('#direccionModal .modal-title').text(title);
|
||||
return;
|
||||
}
|
||||
// Fallback
|
||||
$('#direccionFormModalBody').html('<div class="p-3 text-danger">Error inesperado.</div>');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user