diff --git a/src/main/java/com/imprimelibros/erp/config/InternationalizationConfig.java b/src/main/java/com/imprimelibros/erp/config/InternationalizationConfig.java index b5af439..2553a58 100644 --- a/src/main/java/com/imprimelibros/erp/config/InternationalizationConfig.java +++ b/src/main/java/com/imprimelibros/erp/config/InternationalizationConfig.java @@ -22,15 +22,20 @@ public class InternationalizationConfig implements WebMvcConfigurer { @Bean public LocaleResolver localeResolver() { SessionLocaleResolver slr = new SessionLocaleResolver(); - slr.setDefaultLocale(Locale.of("es")); + slr.setDefaultLocale(Locale.forLanguageTag("es")); // idioma por defecto return slr; } + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + lci.setParamName("lang"); // parámetro ?lang=en, ?lang=es + return lci; + } + @Override public void addInterceptors(InterceptorRegistry registry) { - LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor(); - interceptor.setParamName("lang"); - registry.addInterceptor(interceptor); + registry.addInterceptor(localeChangeInterceptor()); } @Bean @@ -40,22 +45,22 @@ public class InternationalizationConfig implements WebMvcConfigurer { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources("classpath*:i18n/*.properties"); - // Extraer los nombres base sin extensión ni sufijos (_en, _es, etc.) + // Extraer nombres base sin sufijos de idioma Set basenames = Arrays.stream(resources) - .map(res -> { - try { - String uri = Objects.requireNonNull(res.getURI()).toString(); - // Ej: file:/.../i18n/login_en.properties - String path = uri.substring(uri.indexOf("/i18n/") + 1); // i18n/login_en.properties - String base = path.replaceAll("_[a-z]{2}\\.properties$", "") // login.properties - .replaceAll("\\.properties$", ""); - return "classpath:" + base; - } catch (IOException e) { - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); + .map(res -> { + try { + String uri = Objects.requireNonNull(res.getURI()).toString(); + // Ejemplo: file:/.../i18n/login_en.properties + String path = uri.substring(uri.indexOf("/i18n/") + 1); // i18n/login_en.properties + String base = path.replaceAll("_[a-z]{2}\\.properties$", "") // login.properties + .replaceAll("\\.properties$", ""); + return "classpath:" + base; + } catch (IOException e) { + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); source.setBasenames(basenames.toArray(new String[0])); source.setDefaultEncoding("UTF-8"); diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java index f6b172a..65308b6 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoController.java @@ -16,6 +16,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.http.MediaType; @@ -27,6 +28,8 @@ import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto; import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion; import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups; +import jakarta.validation.Valid; + @Controller @RequestMapping("/presupuesto") public class PresupuestoController { @@ -93,7 +96,7 @@ public class PresupuestoController { @PostMapping("/public/validar/cubierta") public ResponseEntity validarCubierta( @Validated(PresupuestoValidationGroups.Cubierta.class) Presupuesto presupuesto, - BindingResult result, + BindingResult result, @RequestParam(name = "calcular", defaultValue = "true") boolean calcular, Locale locale) { @@ -150,6 +153,9 @@ public class PresupuestoController { Map resultado = new HashMap<>(); // servicios extra resultado.putAll(presupuestoService.obtenerServiciosExtras(presupuesto, locale, apiClient)); + Map language = new HashMap<>(); + language.put("calcular", messageSource.getMessage("presupuesto.calcular", null, locale)); + resultado.put("language", language); return ResponseEntity.ok(resultado); } @@ -307,23 +313,35 @@ public class PresupuestoController { } - @GetMapping(value="/public/maquetacion/form", produces = MediaType.TEXT_HTML_VALUE) + @GetMapping(value = "/public/maquetacion/form", produces = MediaType.TEXT_HTML_VALUE) public String getMaquetacionForm(Model model) { model.addAttribute("presupuestoMaquetacion", new PresupuestoMaquetacion()); return "imprimelibros/presupuestos/presupuesto-maquetacion-form :: maquetacionForm"; } - @GetMapping("/public/maquetacion") public ResponseEntity getPresupuestoMaquetacion( - PresupuestoMaquetacion presupuestoMaquetacion, - Model model, Locale locale) { - Map resultado = presupuestoService.getPrecioMaquetacion(presupuestoMaquetacion); - if((Double)resultado.get("precio") == 0.0 && (Integer)resultado.get("numPaginasEstimadas") == 0 - && (Double)resultado.get("precioPaginaEstimado") == 0.0){ - return ResponseEntity.badRequest().body(messageSource.getMessage("presupuesto.errores.presupuesto-maquetacion", null, locale)); - } - return ResponseEntity.ok(resultado); + @Valid @ModelAttribute PresupuestoMaquetacion presupuestoMaquetacion, + BindingResult result, + Locale locale) { + + if (result.hasErrors()) { + // Construimos un mapa field -> mensaje para tu AJAX + Map errores = result.getFieldErrors().stream() + .collect(java.util.stream.Collectors.toMap( + fe -> fe.getField(), + fe -> fe.getDefaultMessage(), + (a, b) -> a)); + return ResponseEntity.badRequest().body(errores); + } + + Map resultado = presupuestoService.getPrecioMaquetacion(presupuestoMaquetacion, locale); + if ((Double) resultado.get("precio") == 0.0 && (Integer) resultado.get("numPaginasEstimadas") == 0 + && (Double) resultado.get("precioPaginaEstimado") == 0.0) { + return ResponseEntity.badRequest() + .body(messageSource.getMessage("presupuesto.errores.presupuesto-maquetacion", null, locale)); + } + return ResponseEntity.ok(resultado); } - + } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoService.java b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoService.java index b8ccddc..415572c 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoService.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/PresupuestoService.java @@ -593,7 +593,7 @@ public class PresupuestoService { return price_prototipo; } - public HashMap getPrecioMaquetacion(PresupuestoMaquetacion presupuestoMaquetacion) { + public HashMap getPrecioMaquetacion(PresupuestoMaquetacion presupuestoMaquetacion, Locale locale) { try { List lista = maquetacionPreciosRepository.findAll(); @@ -657,6 +657,11 @@ public class PresupuestoService { out.put("precio", precioRedondeado.doubleValue()); out.put("numPaginasEstimadas", numPaginas); out.put("precioPaginaEstimado", precioPaginaEstimado); + HashMap language = new HashMap<>(); + language.put("add_to_presupuesto", messageSource.getMessage("presupuesto.add-to-presupuesto", null, locale)); + language.put("cancel", messageSource.getMessage("app.cancelar", null, locale)); + language.put("presupuesto_maquetacion", messageSource.getMessage("presupuesto.maquetacion", null, locale)); + out.put("language", language); return out; } catch (Exception e) { diff --git a/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoMaquetacion.java b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoMaquetacion.java index a2e8568..063537b 100644 --- a/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoMaquetacion.java +++ b/src/main/java/com/imprimelibros/erp/presupuesto/classes/PresupuestoMaquetacion.java @@ -1,25 +1,36 @@ package com.imprimelibros.erp.presupuesto.classes; import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatrices.FontSize; -import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatrices.Formato;; +import com.imprimelibros.erp.presupuesto.maquetacion.MaquetacionMatrices.Formato; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; public class PresupuestoMaquetacion { - private int numCaracteres = 200000; + @NotNull(message = "{validation.required}") + @Min(value = 1, message = "{validation.min}") + private Integer numCaracteres = 200000; private Formato formato = Formato.A5; private FontSize cuerpoTexto = FontSize.medium; - private int numTablas = 0; - private int numColumnas = 1; - private int numFotos = 0; + @Min(value = 0, message = "{validation.min}") + @NotNull(message = "{validation.required}") + private Integer numTablas = 0; + @Min(value = 1, message = "{validation.min}") + @NotNull(message = "{validation.required}") + private Integer numColumnas = 1; + @Min(value = 0, message = "{validation.min}") + @NotNull(message = "{validation.required}") + private Integer numFotos = 0; private boolean correccionOrtotipografica = false; private boolean textoMecanografiado = false; private boolean disenioPortada = false; private boolean epub = false; - public int getNumCaracteres() { + public Integer getNumCaracteres() { return numCaracteres; } - public void setNumCaracteres(int numCaracteres) { + public void setNumCaracteres(Integer numCaracteres) { this.numCaracteres = numCaracteres; } public Formato getFormato() { @@ -34,22 +45,22 @@ public class PresupuestoMaquetacion { public void setCuerpoTexto(FontSize cuerpoTexto) { this.cuerpoTexto = cuerpoTexto; } - public int getNumTablas() { + public Integer getNumTablas() { return numTablas; } - public void setNumTablas(int numTablas) { + public void setNumTablas(Integer numTablas) { this.numTablas = numTablas; } - public int getNumColumnas() { + public Integer getNumColumnas() { return numColumnas; } - public void setNumColumnas(int numColumnas) { + public void setNumColumnas(Integer numColumnas) { this.numColumnas = numColumnas; } - public int getNumFotos() { + public Integer getNumFotos() { return numFotos; } - public void setNumFotos(int numFotos) { + public void setNumFotos(Integer numFotos) { this.numFotos = numFotos; } public boolean isCorreccionOrtotipografica() { diff --git a/src/main/resources/i18n/app_en.properties b/src/main/resources/i18n/app_en.properties index b2b53d4..8abecb4 100644 --- a/src/main/resources/i18n/app_en.properties +++ b/src/main/resources/i18n/app_en.properties @@ -1 +1,9 @@ -app.currency-symbol=€ \ No newline at end of file +app.currency-symbol=€ +app.yes=Yes +app.no=No +app.aceptar=Accept +app.cancelar=Cancel +app.guardar=Save +app.editar=Edit +app.eliminar=Delete +app.imprimir=Print \ No newline at end of file diff --git a/src/main/resources/i18n/app_es.properties b/src/main/resources/i18n/app_es.properties index 0bc744b..59a5250 100644 --- a/src/main/resources/i18n/app_es.properties +++ b/src/main/resources/i18n/app_es.properties @@ -1,3 +1,9 @@ app.currency-symbol=€ app.yes=Sí -app.no=No \ No newline at end of file +app.no=No +app.aceptar=Aceptar +app.cancelar=Cancelar +app.guardar=Guardar +app.editar=Editar +app.eliminar=Eliminar +app.imprimir=Imprimir \ No newline at end of file diff --git a/src/main/resources/i18n/presupuesto_es.properties b/src/main/resources/i18n/presupuesto_es.properties index 521d2e1..1e34b44 100644 --- a/src/main/resources/i18n/presupuesto_es.properties +++ b/src/main/resources/i18n/presupuesto_es.properties @@ -3,6 +3,7 @@ presupuesto.interior=Interior presupuesto.cubierta=Cubierta presupuesto.seleccion-tirada=Seleccion de tirada presupuesto.extras=Extras +presupuesto.add-to-presupuesto=Añadir al presupuesto # Pestaña datos generales de presupuesto presupuesto.informacion-libro=Información del libro @@ -148,6 +149,7 @@ presupuesto.volver-cubierta=Volver a diseño cubierta presupuesto.finalizar=Finalizar presupuesto presupuesto.calcular-presupuesto=Calcular presupuesto presupuesto.consultar-soporte=Consultar con soporte +presupuesto.calcular=Calcular # Resumen del presupuesto presupuesto.resumen-presupuesto=Resumen presupuesto @@ -160,6 +162,7 @@ presupuesto.paginas=Páginas presupuesto.solapas=Solapas presupuesto.papel-gramaje=Papel y gramaje +# Presupuesto de maquetación presupuesto.maquetacion=Presupuesto de maquetación presupuesto.maquetacion.num-caracteres=Número de caracteres presupuesto.maquetacion.num-caracteres-descripcion=Caracteres con espacios (obtenidos desde Word) diff --git a/src/main/resources/i18n/validation_en.properties b/src/main/resources/i18n/validation_en.properties new file mode 100644 index 0000000..c8b1f2d --- /dev/null +++ b/src/main/resources/i18n/validation_en.properties @@ -0,0 +1,6 @@ +validation.required=Required field +validation.number=The field must be a valid number +validation.min=The minimum value is {value} +validation.max=The maximum value is {value} +validation.typeMismatchMsg=Invalid data type +validation.patternMsg=Invalid format \ No newline at end of file diff --git a/src/main/resources/i18n/validation_es.properties b/src/main/resources/i18n/validation_es.properties new file mode 100644 index 0000000..1cb659d --- /dev/null +++ b/src/main/resources/i18n/validation_es.properties @@ -0,0 +1,8 @@ +validation.required=El campo es obligatorio +validation.number=El campo debe ser un número válido +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 + + diff --git a/src/main/resources/static/assets/js/app.js b/src/main/resources/static/assets/js/app.js index 6b0cb17..99d8279 100644 --- a/src/main/resources/static/assets/js/app.js +++ b/src/main/resources/static/assets/js/app.js @@ -1,37 +1,54 @@ (function () { - "use strict"; + "use strict"; - const default_lang = "es"; - const language = localStorage.getItem("language"); + const DEFAULT_LANG = "es"; - function initLanguage() { - const saved = localStorage.getItem("language") || default_lang; - setLanguage(saved, false); // solo actualiza bandera y lang - document.querySelectorAll('.language').forEach(a => { - a.addEventListener('click', () => setLanguage(a.dataset.lang, true)); - }); - } + function getCurrentLang() { + // Viene del servidor (Thymeleaf): + return document.documentElement.lang || DEFAULT_LANG; + } - function setLanguage(lang, redirect = true) { - const already = document.documentElement.lang === lang; + function setFlag(lang) { + const img = document.getElementById("header-lang-img"); + if (!img) return; + img.src = (lang === "en") + ? "/assets/images/flags/gb.svg" + : "/assets/images/flags/spain.svg"; + } - // Actualiza y bandera - document.documentElement.lang = lang; - document.getElementById("header-lang-img").src = - lang === "en" ? "/assets/images/flags/gb.svg" - : "/assets/images/flags/spain.svg"; - localStorage.setItem("language", lang); + function onLanguageClick(e) { + e.preventDefault(); + const lang = this.dataset.lang; + if (!lang || lang === getCurrentLang()) return; - // Redirige si cambia el idioma - if (!already && redirect) { - const url = new URL(location.href); - url.searchParams.set("lang", lang); - location.href = url.toString(); - } - } + // Guarda la preferencia (opcional) + try { localStorage.setItem("language", lang); } catch {} - // Llama al inicializador de idioma en cuanto el DOM esté listo - document.addEventListener("DOMContentLoaded", function () { - initLanguage(); - }); + // Redirige con ?lang=... para que Spring cambie el Locale y renderice en ese idioma + const url = new URL(location.href); + url.searchParams.set("lang", lang); + location.assign(url); + } + + function initLanguage() { + // Usa el idioma actual que viene del servidor + const current = getCurrentLang(); + setFlag(current); + + // Enlaces/ botones de idioma: .language[data-lang="en|es"] + document.querySelectorAll(".language").forEach(a => { + a.addEventListener("click", onLanguageClick); + }); + + // (Opcional) si guardaste algo en localStorage y quieres forzar + // la alineación al entrar por 1ª vez: + const saved = localStorage.getItem("language"); + if (saved && saved !== current) { + const url = new URL(location.href); + url.searchParams.set("lang", saved); + location.replace(url); // alinea y no deja historial extra + } + } + + document.addEventListener("DOMContentLoaded", initLanguage); })(); diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuestador.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuestador.js index 2e754b9..4a81990 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuestador.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuestador.js @@ -129,6 +129,7 @@ class PresupuestoCliente { this.summaryTableCubierta = $('#summary-cubierta'); this.summaryTableSobrecubierta = $('#summary-sobrecubierta'); this.summaryTableFaja = $('#summary-faja'); + this.summaryTableExtras = $('#summary-servicios-extras'); } init() { @@ -1280,6 +1281,7 @@ class PresupuestoCliente { type: 'POST', data: data, success: (data) => { + this.divExtras.data('language-calcular', data.language.calcular); this.#loadExtrasData(data.servicios_extra); this.#changeTab('pills-extras'); }, @@ -1350,15 +1352,22 @@ class PresupuestoCliente { if (id === 'btn-prev-extras') { this.#changeTab('pills-seleccion-tirada'); + this.summaryTableExtras.addClass('d-none'); } else { //this.#changeTab('pills-finalizar'); } }); + + // Eventos para el resumen + $(document).on('change', '.service-checkbox', (e) => { + Summary.updateExtras(); + }); } #loadExtrasData(servicios) { this.divExtras.empty(); + this.summaryTableExtras.removeClass('d-none'); this.divExtras.removeClass('animate-fadeInUpBounce'); this.divExtras.addClass('animate-fadeInUpBounce'); @@ -1366,6 +1375,8 @@ class PresupuestoCliente { const item = new ServiceOptionCard(extra); this.divExtras.append(item.render()); } + + Summary.updateExtras(); } diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-maquetacion.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-maquetacion.js index e4a02f4..efc2f65 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-maquetacion.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/presupuesto-maquetacion.js @@ -1,9 +1,19 @@ -$(document).on('click', '#maquetacion', function (e) { +import * as Summary from "./summary.js"; + +$(document).on('change', '#maquetacion', function (e) { e.preventDefault(); - $.get("/presupuesto/public/maquetacion/form", function (data) { - $("#maquetacionModalBody").html(data); - $("#maquetacionModal").modal("show"); - }); + if ($('#maquetacion').is(':checked')) { + $.get("/presupuesto/public/maquetacion/form", function (data) { + $("#maquetacionModalBody").html(data); + $("#maquetacionModal").modal("show"); + }); + } else { + const calcularStr = $('#div-extras').data('language-calcular'); + $('#maquetacion').data('price', calcularStr); + $('label[for="maquetacion"] .service-price') + .text(calcularStr); + $('#maquetacion').prop('checked', false); + } }); $(document).on("submit", "#maquetacionForm", function (e) { @@ -15,15 +25,91 @@ $(document).on("submit", "#maquetacionForm", function (e) { url: $form.attr("action"), type: $form.attr("method"), data: $form.serialize(), - success: function (data) { - // obtener el json devuelto - const json = JSON.parse(data); - + dataType: "json", + success: function (json) { + + const modalEl = document.getElementById("maquetacionModal"); + const modal = bootstrap.Modal.getInstance(modalEl) || new bootstrap.Modal(modalEl); + modal.hide(); + + + const resumenHtml = ` +
+

Páginas calculadas: ${json.numPaginasEstimadas ?? "-"}

+

Precio por página estimado: ${formateaMoneda(json.precioPaginaEstimado) || "-"}

+
+ ${json.precio ? + `

Precio: ${formateaMoneda(json.precio)}

` : ""} +
+ `; + + Swal.fire({ + title: json.language.presupuesto_maquetacion || 'Presupuesto Maquetación', + html: resumenHtml, + icon: 'info', + showCancelButton: true, + confirmButtonText: json.language.add_to_presupuesto || 'Añadir al presupuesto', + cancelButtonText: json.language.cancel || 'Cancelar', + customClass: { + confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar + cancelButton: 'btn btn-light' // clases para cancelar + }, + buttonsStyling: false, + reverseButtons: false, + allowOutsideClick: false + }).then((result) => { + if (result.isConfirmed) { + $('#maquetacion').prop('checked', true); + $('#maquetacion').data('price', json.precio); + $('label[for="maquetacion"] .service-price') + .text(formateaMoneda(json.precio)); + Summary.updateExtras(); + } + else { + const calcularStr = $('#div-extras').data('language-calcular'); + $('#maquetacion').prop('checked', false); + $('#maquetacion').data('price', calcularStr); + $('label[for="maquetacion"] .service-price').text(calcularStr); + } + }); }, error: function (xhr, status, error) { - $("#maquetacionModalBody").html( - "
" + xhr.responseText + "
" - ); + try { + const errs = JSON.parse(xhr.responseText); // { numCaracteres: "…" , … } + // limpia errores previos + $form.find(".is-invalid").removeClass("is-invalid"); + $form.find(".invalid-feedback").text(""); + + // recorre los errores y los muestra + Object.entries(errs).forEach(([field, msg]) => { + const $input = $form.find(`[name="${field}"]`); + if ($input.length) { + $input.addClass("is-invalid"); + $input.siblings(".invalid-feedback").text(msg); + } + }); + } catch { + $("#maquetacionModalBody").html( + "
" + xhr.responseText + "
" + ); + } } }); -}); \ No newline at end of file +}); + +$(document).on('hidden.bs.modal', '#maquetacionModal', function () { + + const calcularStr = $('#div-extras').data('language-calcular'); + $('#maquetacion').prop('checked', false); + $('#maquetacion').data('price', calcularStr); + $('label[for="maquetacion"] .service-price').text(calcularStr); +}); + +function formateaMoneda(valor) { + try { + return new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'EUR' }).format(valor); + } catch { + return valor; + } +} + diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/summary.js b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/summary.js index eb1cb64..84032f1 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/summary.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/presupuestador/summary.js @@ -138,4 +138,27 @@ export function updateFaja() { $('#summary-faja-tamanio-solapa').text($('#tamanio-solapas-faja').val() + ' mm'); $('#summary-faja-acabado').text($('#faja-acabado option:selected').text()); } +} + +export function updateExtras() { + const $table = $('#summary-servicios-extras'); + const $tbody = $table.find('tbody'); + $tbody.empty(); + + // Agregar las filas de servicios extras + $('.service-checkbox:checked').each(function() { + const $servicio = $(this); + const resumen = $(`label[for="${$servicio.attr('id')}"] .service-title`).text().trim() || $servicio.attr('id'); + const price = $(`label[for="${$servicio.attr('id')}"] .service-price`).text().trim() || $servicio.attr('price'); + const $row = $('').append( + $('').append($('').text(resumen)), + $('').text(price) + ); + $tbody.append($row); + }); + if ($tbody.children().length > 0) { + $table.removeClass('d-none'); + } else { + $table.addClass('d-none'); + } } \ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_summary.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_summary.html index 78548ff..d181f17 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_summary.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuestador-items/_summary.html @@ -205,6 +205,15 @@ + + + + + + + + +
Servicios
diff --git a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-maquetacion-form.html b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-maquetacion-form.html index 7802a1d..877cdbd 100644 --- a/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-maquetacion-form.html +++ b/src/main/resources/templates/imprimelibros/presupuestos/presupuesto-maquetacion-form.html @@ -1,8 +1,9 @@
-
+
+
@@ -27,15 +28,18 @@
+
+
+
-