implementado duplicar en la lista

This commit is contained in:
2025-11-29 23:30:22 +01:00
parent 58fd4815c6
commit c6e2322132
13 changed files with 5128 additions and 25 deletions

View File

@ -11,6 +11,11 @@ presupuesto.add-to-presupuesto=Añadir al presupuesto
presupuesto.calcular=Calcular
presupuesto.add=Añadir presupuesto
presupuesto.guardar=Guardar
presupuesto.duplicar=Duplicar
presupuesto.reimprimir=Reimprimir
presupuesto.editar=Editar
presupuesto.ver=Ver
presupuesto.borrar=Eliminar
presupuesto.add-to-cart=Añadir a la cesta
presupuesto.nav.presupuestos-cliente=Presupuestos cliente
@ -296,6 +301,18 @@ presupuesto.error.delete-permission-denied=No se puede eliminar: permiso denegad
presupuesto.error.delete-not-found=No se puede eliminar: presupuesto no encontrado.
presupuesto.error.delete-not-draft=Solo se pueden eliminar presupuestos en estado Borrador.
# Mensajes de duplicar presupuesto
presupuesto.duplicar.title=Duplicar presupuesto
presupuesto.duplicar.confirm=Si, DUPLICAR
presupuesto.duplicar.cancelar=Cancelar
presupuesto.duplicar.text=¿Está seguro de que desea duplicar este presupuesto?<br>Se creará una copia exacta del mismo en estado Borrador con el título introducido a continuación.
presupuesto.duplicar.required=El título es obligatorio.
presupuesto.duplicar.success.title=Presupuesto duplicado
presupuesto.duplicar.success.text=El presupuesto ha sido duplicado con éxito.
presupuesto.duplicar.aceptar=Aceptar.
presupuesto.duplicar.error.title=Error al duplicar presupuesto
presupuesto.duplicar.error.internal=No se puede duplicar: error interno.
# Añadir presupuesto
presupuesto.add.tipo=Tipo de presupuesto
presupuesto.add.anonimo=Anónimo

View File

@ -42,7 +42,41 @@ $(() => {
]
}
}
new Quill(item, snowEditorData);
var quill = new Quill(item, snowEditorData);
var initialContent = item.dataset.contenido || "";
// Contenido inicial desde Thymeleaf
var initialContent = item.dataset.contenido || "";
if (initialContent) {
if(initialContent.trim() !== "" && initialContent.trim() !== "<p><br></p>")
$('.badge-comentario').removeClass('d-none');
quill.clipboard.dangerouslyPasteHTML(initialContent);
}
quill.root.addEventListener("blur", function () {
let contenido = quill.root.innerHTML;
if(contenido.trim() !== "" && contenido.trim() !== "<p><br></p>"){
$('.badge-comentario').removeClass('d-none');
} else {
$('.badge-comentario').addClass('d-none');
}
if ($('#presupuesto_id').length > 0 && $('#presupuesto_id').val() !== "") {
$.ajax({
url: "/presupuesto/" + $('#presupuesto_id').val() + "/comentario",
method: "POST",
data: {
comentario: contenido
},
success: function () {
}
});
}
});
});
}
});

View File

@ -402,8 +402,6 @@ export default class PresupuestoWizard {
...this.#getInteriorData(),
...this.#getCubiertaData(),
selectedTirada: this.formData.selectedTirada
};
const sobrecubierta = data.sobrecubierta;

View File

@ -1,4 +1,4 @@
import { preguntarTipoPresupuesto } from './presupuesto-utils.js';
import { preguntarTipoPresupuesto, duplicar } from './presupuesto-utils.js';
(() => {
// si jQuery está cargado, añade CSRF a AJAX
@ -200,6 +200,17 @@ import { preguntarTipoPresupuesto } from './presupuesto-utils.js';
}
});
$('#presupuestos-clientes-datatable').on('click', '.btn-duplicate-privado', function (e) {
e.preventDefault();
const id = $(this).data('id');
let data = table_clientes.row($(this).parents('tr')).data();
const tituloOriginal = data.titulo;
duplicar(id, tituloOriginal);
});
$('#presupuestos-clientes-datatable').on('click', '.btn-delete-privado', function (e) {
e.preventDefault();

View File

@ -75,3 +75,94 @@ export async function preguntarTipoPresupuesto() {
}
}).then((r) => (r.isConfirmed ? r.value : null));
}
export function duplicar(id, titulo) {
// swal with input
Swal.fire({
title: window.languageBundle.get(['presupuesto.duplicar.title']) || 'Duplicar presupuesto',
html: window.languageBundle.get(['presupuesto.duplicar.text']) || `¿Deseas duplicar el presupuesto "${titulo}"?`,
icon: 'question',
input: 'text',
inputValue: `[D] ${titulo}`,
showCancelButton: true,
confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.confirm']) || 'Sí, duplicar',
cancelButtonText: window.languageBundle.get(['presupuesto.duplicar.cancelar']) || 'Cancelar',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
cancelButton: 'btn btn-light' // clases para cancelar
},
inputValidator: (value) => {
if (!value) {
return window.languageBundle.get(['presupuesto.duplicar.required']) || 'El título no puede estar vacío';
}
},
}).then((result) => {
if (result.isConfirmed) {
const tituloNuevo = result.value;
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);
}
});
}
$.ajax({
url: `presupuesto/duplicar/${id}`,
data: {
titulo: tituloNuevo,
},
method: 'POST',
success: function (response) {
if (response.id && response.id > 0) {
Swal.fire({
title: window.languageBundle.get(['presupuesto.duplicar.success.title']) || 'Presupuesto duplicado',
text: window.languageBundle.get(['presupuesto.duplicar.success.text']) || `El presupuesto "${titulo}" ha sido duplicado correctamente.`,
icon: 'success',
confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.aceptar']) || 'Aceptar',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary' // clases para el botón confirmar
},
}).then(() => {
// Recargar la página o redirigir a la lista de presupuestos
window.location.href = '/presupuesto/edit/' + response.id;
});
} else {
Swal.fire({
title: window.languageBundle.get(['presupuesto.duplicar.error.title']) || 'Error al duplicar',
text: response.message || window.languageBundle.get(['presupuesto.duplicar.error.internal']) || 'Ha ocurrido un error al duplicar el presupuesto.',
icon: 'error',
confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.aceptar']) || 'Aceptar',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary' // clases para el botón confirmar
},
});
}
},
error: function (xhr) {
Swal.fire({
title: window.languageBundle.get(['presupuesto.duplicar.error.title']) || 'Error al duplicar',
text: xhr.responseJSON?.message || window.languageBundle.get(['presupuesto.duplicar.error.internal']) || 'Ha ocurrido un error al duplicar el presupuesto.',
icon: 'error',
confirmButtonText: window.languageBundle.get(['presupuesto.duplicar.aceptar']) || 'Aceptar',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-secondary' // clases para el botón confirmar
},
});
}
});
};
});
}

View File

@ -77,7 +77,9 @@
</thead>
<tbody>
<tr th:if="${resumen['linea0']}">
<td><img style="max-width: 60px; height: auto;" th:src="${resumen['imagen']}" th:alt="${resumen['imagen_alt']}" class="img-fluid" /></td>
<td><img style="max-width: 60px; height: auto;"
th:src="${resumen['imagen']}" th:alt="${resumen['imagen_alt']}"
class="img-fluid" /></td>
<td class="text-start" th:utext="${resumen['linea0'].descripcion}">
Descripción 1</td>
<td class="text-end" th:text="${resumen['linea0'].cantidad}">1</td>
@ -165,6 +167,38 @@
</button>
</div>
<div sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')">
<div class="accordion lefticon-accordion custom-accordionwithicon accordion-border-box mt-3"
id="accordionlefticon">
<div class="accordion-item material-shadow">
<h2 class="accordion-header" id="accordionComentario">
<button class="accordion-button collapsed" type="button"
data-bs-toggle="collapse"
data-bs-target="#accor_accordionComentario" aria-expanded="false"
aria-controls="accor_accordionComentario">
<span
th:text="#{presupuesto.comentario-administrador}">Comentario</span>
<span class="d-none badge badge-comentario bg-danger ms-1">!</span>
</button>
</h2>
<div id="accor_accordionComentario" class="accordion-collapse collapse"
aria-labelledby="accordionComentario"
data-bs-parent="#accordionlefticon">
<div class="accordion-body">
<div class="snow-editor" id="comentario" name="comentario"
th:attr="data-contenido=${presupuesto.comentario} "
style=" height: 300px;">
</div> <!-- end Snow-editor-->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -147,16 +147,19 @@
<h2 class="accordion-header" id="accordionComentario">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#accor_accordionComentario" aria-expanded="false"
aria-controls="accor_accordionComentario"
th:text="#{presupuesto.comentario-administrador}">Comentario
aria-controls="accor_accordionComentario">
<span th:text="#{presupuesto.comentario-administrador}">Comentario</span>
<span class="d-none badge badge-comentario bg-danger ms-1">!</span>
</button>
</h2>
<div id="accor_accordionComentario" class="accordion-collapse collapse"
aria-labelledby="accordionComentario" data-bs-parent="#accordionlefticon">
<div class="accordion-body">
<div class="snow-editor" name="comentario"
th:text="@{presupuesto.comentario}" style=" height: 300px;">
<div class="snow-editor" id="comentario" name="comentario"
th:attr="data-contenido=${presupuesto.comentario} "
style=" height: 300px;">
</div> <!-- end Snow-editor-->
</div>
</div>

View File

@ -12,7 +12,7 @@
<th scope="col" th:text="#{presupuesto.tabla.estado}">Estado</th>
<th scope="col" th:text="#{presupuesto.tabla.total-iva}">Total con IVA</th>
<th scope="col" th:text="#{presupuesto.tabla.updated-at}">Actualizado el</th>
<th scope="col" th:text="#{presupuesto.tabla.acciones}">Acciones</th>
<th style="min-width: 100px;" scope="col" th:text="#{presupuesto.tabla.acciones}">Acciones</th>
</tr>
<tr>
<th><input type="text" class="form-control form-control-sm presupuesto-filter" data-col="id" /></th>

View File

@ -13,7 +13,7 @@
<th scope="col" th:text="#{presupuesto.tabla.estado}">Estado</th>
<th scope="col" th:text="#{presupuesto.tabla.total-iva}">Total con IVA</th>
<th scope="col" th:text="#{presupuesto.tabla.updated-at}">Actualizado el</th>
<th scope="col" th:text="#{presupuesto.tabla.acciones}">Acciones</th>
<th style="min-width: 100px;" scope="col" th:text="#{presupuesto.tabla.acciones}">Acciones</th>
</tr>
<tr>
<th><input type="text" class="form-control form-control-sm presupuesto-filter" data-col="id" /></th>