mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
trabajando en la vista del carro de la compra
This commit is contained in:
@ -10,6 +10,7 @@ import org.springframework.security.core.Authentication;
|
|||||||
|
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/cart")
|
@RequestMapping("/cart")
|
||||||
@ -46,15 +47,15 @@ public class CartController {
|
|||||||
|
|
||||||
/** Vista del carrito */
|
/** Vista del carrito */
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public String viewCart(Model model, Principal principal) {
|
public String viewCart(Model model, Principal principal, Locale locale) {
|
||||||
var items = service.listItems(currentUserId(principal));
|
var items = service.listItems(currentUserId(principal), locale);
|
||||||
model.addAttribute("items", items);
|
model.addAttribute("items", items);
|
||||||
return "imprimelibros/cart/cart"; // crea esta vista si quieres (tabla simple)
|
return "imprimelibros/cart/cart"; // crea esta vista si quieres (tabla simple)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Añadir presupuesto via POST form */
|
/** Añadir presupuesto via POST form */
|
||||||
@PostMapping("/add")
|
@PostMapping("/add")
|
||||||
public String add(@RequestParam("presupuestoId") Long presupuestoId, Principal principal) {
|
public String add(@PathVariable(name = "presupuestoId", required = true) Long presupuestoId, Principal principal) {
|
||||||
service.addPresupuesto(currentUserId(principal), presupuestoId);
|
service.addPresupuesto(currentUserId(principal), presupuestoId);
|
||||||
return "redirect:/cart";
|
return "redirect:/cart";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,45 @@
|
|||||||
package com.imprimelibros.erp.cart;
|
package com.imprimelibros.erp.cart;
|
||||||
|
|
||||||
|
import jakarta.mail.Message;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.imprimelibros.erp.presupuesto.classes.PresupuestoFormatter;
|
||||||
|
import com.imprimelibros.erp.presupuesto.dto.Presupuesto;
|
||||||
|
import com.imprimelibros.erp.presupuesto.PresupuestoRepository;
|
||||||
|
import com.imprimelibros.erp.presupuesto.service.PresupuestoService;
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class CartService {
|
public class CartService {
|
||||||
|
|
||||||
private final CartRepository cartRepo;
|
private final CartRepository cartRepo;
|
||||||
private final CartItemRepository itemRepo;
|
private final CartItemRepository itemRepo;
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
private final PresupuestoFormatter presupuestoFormatter;
|
||||||
|
private final PresupuestoRepository presupuestoRepo;
|
||||||
|
|
||||||
public CartService(CartRepository cartRepo, CartItemRepository itemRepo) {
|
public CartService(CartRepository cartRepo, CartItemRepository itemRepo,
|
||||||
|
MessageSource messageSource, PresupuestoFormatter presupuestoFormatter,
|
||||||
|
PresupuestoRepository presupuestoRepo) {
|
||||||
this.cartRepo = cartRepo;
|
this.cartRepo = cartRepo;
|
||||||
this.itemRepo = itemRepo;
|
this.itemRepo = itemRepo;
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
this.presupuestoFormatter = presupuestoFormatter;
|
||||||
|
this.presupuestoRepo = presupuestoRepo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Devuelve el carrito activo o lo crea si no existe. */
|
/** Devuelve el carrito activo o lo crea si no existe. */
|
||||||
@ -30,9 +56,21 @@ public class CartService {
|
|||||||
|
|
||||||
/** Lista items (presupuestos) del carrito activo del usuario. */
|
/** Lista items (presupuestos) del carrito activo del usuario. */
|
||||||
@Transactional
|
@Transactional
|
||||||
public List<CartItem> listItems(Long userId) {
|
public List<Map<String, Object>> listItems(Long userId, Locale locale) {
|
||||||
Cart cart = getOrCreateActiveCart(userId);
|
Cart cart = getOrCreateActiveCart(userId);
|
||||||
return itemRepo.findByCartId(cart.getId());
|
List<Map<String, Object>> resultados = new ArrayList<>();
|
||||||
|
List<CartItem> items = itemRepo.findByCartId(cart.getId());
|
||||||
|
for (CartItem item : items) {
|
||||||
|
|
||||||
|
Presupuesto p = presupuestoRepo.findById(item.getPresupuestoId())
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Presupuesto no encontrado: " + item.getPresupuestoId()));
|
||||||
|
|
||||||
|
this.getElementoCart(p, locale);
|
||||||
|
Map<String, Object> elemento = getElementoCart(p, locale);
|
||||||
|
resultados.add(elemento);
|
||||||
|
}
|
||||||
|
System.out.println("Cart items: " + resultados);
|
||||||
|
return resultados;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Añade un presupuesto al carrito. Si ya está, no hace nada (idempotente). */
|
/** Añade un presupuesto al carrito. Si ya está, no hace nada (idempotente). */
|
||||||
@ -86,4 +124,73 @@ public class CartService {
|
|||||||
Cart cart = getOrCreateActiveCart(userId);
|
Cart cart = getOrCreateActiveCart(userId);
|
||||||
return itemRepo.findByCartId(cart.getId()).size();
|
return itemRepo.findByCartId(cart.getId()).size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getElementoCart(Presupuesto presupuesto, Locale locale) {
|
||||||
|
|
||||||
|
Map<String, Object> resumen = new HashMap<>();
|
||||||
|
|
||||||
|
resumen.put("titulo", presupuesto.getTitulo());
|
||||||
|
|
||||||
|
resumen.put("imagen",
|
||||||
|
"/assets/images/imprimelibros/presupuestador/" + presupuesto.getTipoEncuadernacion() + ".png");
|
||||||
|
resumen.put("imagen_alt",
|
||||||
|
messageSource.getMessage("presupuesto." + presupuesto.getTipoEncuadernacion(), null, locale));
|
||||||
|
|
||||||
|
resumen.put("presupuestoId", presupuesto.getId());
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
List<Map<String, Object>> servicios = new ArrayList<>();
|
||||||
|
if (presupuesto.getServiciosJson() != null && !presupuesto.getServiciosJson().isBlank())
|
||||||
|
try{
|
||||||
|
servicios = mapper.readValue(presupuesto.getServiciosJson(), new TypeReference<>() {
|
||||||
|
});
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
// Manejar la excepción
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hayDepositoLegal = servicios != null && servicios.stream()
|
||||||
|
.map(m -> java.util.Objects.toString(m.get("id"), ""))
|
||||||
|
.map(String::trim)
|
||||||
|
.anyMatch("deposito-legal"::equals);
|
||||||
|
|
||||||
|
List<HashMap<String, Object>> lineas = new ArrayList<>();
|
||||||
|
HashMap<String, Object> linea = new HashMap<>();
|
||||||
|
Double precio_unitario = 0.0;
|
||||||
|
Double precio_total = 0.0;
|
||||||
|
linea.put("descripcion", presupuestoFormatter.resumen(presupuesto, servicios, locale));
|
||||||
|
linea.put("cantidad", presupuesto.getSelectedTirada() != null ? presupuesto.getSelectedTirada() : 0);
|
||||||
|
precio_unitario = (presupuesto.getPrecioUnitario() != null ? presupuesto.getPrecioUnitario().doubleValue() : 0.0);
|
||||||
|
precio_total = (presupuesto.getPrecioTotalTirada() != null ? presupuesto.getPrecioTotalTirada().doubleValue() : 0.0);
|
||||||
|
linea.put("precio_unitario", precio_unitario);
|
||||||
|
linea.put("precio_total", BigDecimal.valueOf(precio_total).setScale(2, RoundingMode.HALF_UP));
|
||||||
|
lineas.add(linea);
|
||||||
|
|
||||||
|
if (hayDepositoLegal) {
|
||||||
|
linea = new HashMap<>();
|
||||||
|
linea.put("descripcion", messageSource.getMessage("presupuesto.resumen-deposito-legal", null, locale));
|
||||||
|
linea.put("cantidad", 4);
|
||||||
|
linea.put("precio_unitario", precio_unitario);
|
||||||
|
linea.put("precio_total", BigDecimal.valueOf(precio_unitario * 4).setScale(2, RoundingMode.HALF_UP));
|
||||||
|
lineas.add(linea);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, Object>> serviciosExtras = new ArrayList<>();
|
||||||
|
if (servicios != null) {
|
||||||
|
for (Map<String, Object> servicio : servicios) {
|
||||||
|
HashMap<String, Object> servicioData = new HashMap<>();
|
||||||
|
servicioData.put("id", servicio.get("id"));
|
||||||
|
servicioData.put("descripcion", servicio.get("label"));
|
||||||
|
servicioData.put("precio", servicio.get("id").equals("marcapaginas")
|
||||||
|
? Double.parseDouble(servicio.get("price").toString())
|
||||||
|
/ Double.parseDouble(servicio.get("units").toString())
|
||||||
|
: servicio.get("price"));
|
||||||
|
servicioData.put("unidades", servicio.get("units"));
|
||||||
|
serviciosExtras.add(servicioData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resumen.put("lineas", lineas);
|
||||||
|
resumen.put("servicios", serviciosExtras);
|
||||||
|
|
||||||
|
return resumen;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/main/resources/i18n/cart_en.properties
Normal file
1
src/main/resources/i18n/cart_en.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
4
src/main/resources/i18n/cart_es.properties
Normal file
4
src/main/resources/i18n/cart_es.properties
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
cart.title=Cesta de la compra
|
||||||
|
cart.empty=Tu cesta de la compra está vacía.
|
||||||
|
|
||||||
|
cart.item.presupuesto-numero=Presupuesto #
|
||||||
@ -11,6 +11,7 @@ presupuesto.add-to-presupuesto=Añadir al presupuesto
|
|||||||
presupuesto.calcular=Calcular
|
presupuesto.calcular=Calcular
|
||||||
presupuesto.add=Añadir presupuesto
|
presupuesto.add=Añadir presupuesto
|
||||||
presupuesto.guardar=Guardar
|
presupuesto.guardar=Guardar
|
||||||
|
presupuesto.add-to-cart=Añadir a la cesta
|
||||||
|
|
||||||
presupuesto.nav.presupuestos-cliente=Presupuestos cliente
|
presupuesto.nav.presupuestos-cliente=Presupuestos cliente
|
||||||
presupuesto.nav.presupuestos-anonimos=Presupuestos anónimos
|
presupuesto.nav.presupuestos-anonimos=Presupuestos anónimos
|
||||||
@ -20,6 +21,7 @@ presupuesto.estado.aceptado=Aceptado
|
|||||||
presupuesto.estado.modificado=Modificado
|
presupuesto.estado.modificado=Modificado
|
||||||
|
|
||||||
presupuesto.tabla.id=ID
|
presupuesto.tabla.id=ID
|
||||||
|
presupuesto.tabla.numero=Número
|
||||||
presupuesto.tabla.titulo=Título
|
presupuesto.tabla.titulo=Título
|
||||||
presupuesto.tabla.cliente=Cliente
|
presupuesto.tabla.cliente=Cliente
|
||||||
presupuesto.tabla.encuadernacion=Encuadernación
|
presupuesto.tabla.encuadernacion=Encuadernación
|
||||||
@ -100,8 +102,8 @@ presupuesto.offset-blanco-volumen=Offset Blanco Volumen
|
|||||||
presupuesto.offset-ahuesado=Offset Ahuesado
|
presupuesto.offset-ahuesado=Offset Ahuesado
|
||||||
presupuesto.offset-ahuesado-volumen=Offset Ahuesado Volumen
|
presupuesto.offset-ahuesado-volumen=Offset Ahuesado Volumen
|
||||||
presupuesto.estucado-mate=Estucado Mate
|
presupuesto.estucado-mate=Estucado Mate
|
||||||
presupuesto.volver-datos-generales=Volver a datos generales
|
presupuesto.volver-datos-generales=Datos generales
|
||||||
presupuesto.continuar-cubierta=Continuar a diseño cubierta
|
presupuesto.continuar-cubierta=Diseño cubierta
|
||||||
|
|
||||||
# Pestaña cubierta
|
# Pestaña cubierta
|
||||||
presupuesto.plantilla-cubierta=Plantilla de cubierta
|
presupuesto.plantilla-cubierta=Plantilla de cubierta
|
||||||
@ -139,8 +141,8 @@ presupuesto.cartulina-grafica=Cartulina gráfica
|
|||||||
presupuesto.estucado-mate-cubierta=Estucado mate
|
presupuesto.estucado-mate-cubierta=Estucado mate
|
||||||
presupuesto.gramaje-cubierta=Gramaje cubierta
|
presupuesto.gramaje-cubierta=Gramaje cubierta
|
||||||
presupuesto.gramaje-cubierta-descripcion=Seleccione el gramaje para la cubierta
|
presupuesto.gramaje-cubierta-descripcion=Seleccione el gramaje para la cubierta
|
||||||
presupuesto.volver-interior=Volver a diseño interior
|
presupuesto.volver-interior=Diseño interior
|
||||||
presupuesto.continuar-seleccion-tirada=Continuar a selección de tirada
|
presupuesto.continuar-seleccion-tirada=Selección de tirada
|
||||||
presupuesto.offset=Offset
|
presupuesto.offset=Offset
|
||||||
presupuesto.estucado=Estucado
|
presupuesto.estucado=Estucado
|
||||||
presupuesto.verjurado=Verjurado
|
presupuesto.verjurado=Verjurado
|
||||||
@ -176,8 +178,8 @@ presupuesto.precio-unidad=Precio por unidad
|
|||||||
presupuesto.seleccionar-tirada=Seleccionar tirada
|
presupuesto.seleccionar-tirada=Seleccionar tirada
|
||||||
presupuesto.tirada-seleccionada=Seleccionada
|
presupuesto.tirada-seleccionada=Seleccionada
|
||||||
presupuesto.unidades=UNIDADES
|
presupuesto.unidades=UNIDADES
|
||||||
presupuesto.volver-seleccion-tirada=Volver a selección de tirada
|
presupuesto.volver-seleccion-tirada=Selección de tirada
|
||||||
presupuesto.continuar-extras-libro=Continuar a extras del libro
|
presupuesto.continuar-extras-libro=Extras del libro
|
||||||
presupuesto.error-obtener-precio=No se pudo obtener el precio para los datos introducidos. Por favor, contacte con el soporte técnico.
|
presupuesto.error-obtener-precio=No se pudo obtener el precio para los datos introducidos. Por favor, contacte con el soporte técnico.
|
||||||
|
|
||||||
#pestaña extras del libro
|
#pestaña extras del libro
|
||||||
@ -195,7 +197,7 @@ presupuesto.extras-marcapaginas=Marcapáginas
|
|||||||
presupuesto.extras-maquetacion=Maquetación
|
presupuesto.extras-maquetacion=Maquetación
|
||||||
presupuesto.extras-ferro-digital-ribbon=Incluido
|
presupuesto.extras-ferro-digital-ribbon=Incluido
|
||||||
presupuesto.extras-calcular=Calcular
|
presupuesto.extras-calcular=Calcular
|
||||||
presupuesto.volver-cubierta=Volver a diseño cubierta
|
presupuesto.volver-cubierta=Diseño cubierta
|
||||||
presupuesto.finalizar=Finalizar presupuesto
|
presupuesto.finalizar=Finalizar presupuesto
|
||||||
presupuesto.calcular-presupuesto=Calcular presupuesto
|
presupuesto.calcular-presupuesto=Calcular presupuesto
|
||||||
presupuesto.consultar-soporte=Consultar con soporte
|
presupuesto.consultar-soporte=Consultar con soporte
|
||||||
@ -221,7 +223,7 @@ presupuesto.resumen-texto-end=</ul>
|
|||||||
presupuesto.resumen-texto-sobrecubierta=<li>Sobrecubierta impresa en {0} {1} gr. <ul><li>Acabado {2}</li><li>Solapas: {3} mm.</li></ul></li>
|
presupuesto.resumen-texto-sobrecubierta=<li>Sobrecubierta impresa en {0} {1} gr. <ul><li>Acabado {2}</li><li>Solapas: {3} mm.</li></ul></li>
|
||||||
presupuesto.resumen-texto-faja=<li>Faja impresa en {0} {1} gr. con un alto de {2} mm. <ul><li>Acabado {3}</li><li>Solapas: {4} mm.</li></ul></li>
|
presupuesto.resumen-texto-faja=<li>Faja impresa en {0} {1} gr. con un alto de {2} mm. <ul><li>Acabado {3}</li><li>Solapas: {4} mm.</li></ul></li>
|
||||||
presupuesto.resumen-deposito-legal=Ejemplares para el Depósito Legal
|
presupuesto.resumen-deposito-legal=Ejemplares para el Depósito Legal
|
||||||
presupuesto.volver-extras=Volver a extras
|
presupuesto.volver-extras=Extras del libro
|
||||||
presupuesto.resumen.inicie-sesion=Inicie sesión para continuar
|
presupuesto.resumen.inicie-sesion=Inicie sesión para continuar
|
||||||
presupuesto.resumen.agregar-cesta=Agregar a la cesta
|
presupuesto.resumen.agregar-cesta=Agregar a la cesta
|
||||||
|
|
||||||
|
|||||||
@ -49,9 +49,118 @@
|
|||||||
location.replace(url); // alinea y no deja historial extra
|
location.replace(url); // alinea y no deja historial extra
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (document.getElementById("topnav-hamburger-icon")) {
|
||||||
|
document.getElementById("topnav-hamburger-icon").addEventListener("click", toggleHamburgerMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", windowResizeHover);
|
||||||
|
windowResizeHover();
|
||||||
|
|
||||||
initsAlert();
|
initsAlert();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function windowResizeHover() {
|
||||||
|
feather.replace();
|
||||||
|
var windowSize = document.documentElement.clientWidth;
|
||||||
|
if (windowSize < 1025 && windowSize > 767) {
|
||||||
|
document.body.classList.remove("twocolumn-panel");
|
||||||
|
if (sessionStorage.getItem("data-layout") == "twocolumn") {
|
||||||
|
document.documentElement.setAttribute("data-layout", "twocolumn");
|
||||||
|
if (document.getElementById("customizer-layout03")) {
|
||||||
|
document.getElementById("customizer-layout03").click();
|
||||||
|
}
|
||||||
|
twoColumnMenuGenerate();
|
||||||
|
initTwoColumnActiveMenu();
|
||||||
|
isCollapseMenu();
|
||||||
|
}
|
||||||
|
if (sessionStorage.getItem("data-layout") == "vertical") {
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", "sm");
|
||||||
|
}
|
||||||
|
if (document.querySelector(".hamburger-icon")) {
|
||||||
|
document.querySelector(".hamburger-icon").classList.add("open");
|
||||||
|
}
|
||||||
|
} else if (windowSize >= 1025) {
|
||||||
|
document.body.classList.remove("twocolumn-panel");
|
||||||
|
if (sessionStorage.getItem("data-layout") == "twocolumn") {
|
||||||
|
document.documentElement.setAttribute("data-layout", "twocolumn");
|
||||||
|
if (document.getElementById("customizer-layout03")) {
|
||||||
|
document.getElementById("customizer-layout03").click();
|
||||||
|
}
|
||||||
|
twoColumnMenuGenerate();
|
||||||
|
initTwoColumnActiveMenu();
|
||||||
|
isCollapseMenu();
|
||||||
|
}
|
||||||
|
if (sessionStorage.getItem("data-layout") == "vertical") {
|
||||||
|
document.documentElement.setAttribute(
|
||||||
|
"data-sidebar-size",
|
||||||
|
sessionStorage.getItem("data-sidebar-size")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (document.querySelector(".hamburger-icon")) {
|
||||||
|
document.querySelector(".hamburger-icon").classList.remove("open");
|
||||||
|
}
|
||||||
|
} else if (windowSize <= 767) {
|
||||||
|
document.body.classList.remove("vertical-sidebar-enable");
|
||||||
|
document.body.classList.add("twocolumn-panel");
|
||||||
|
if (sessionStorage.getItem("data-layout") == "twocolumn") {
|
||||||
|
document.documentElement.setAttribute("data-layout", "vertical");
|
||||||
|
hideShowLayoutOptions("vertical");
|
||||||
|
isCollapseMenu();
|
||||||
|
}
|
||||||
|
if (sessionStorage.getItem("data-layout") != "horizontal") {
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", "lg");
|
||||||
|
}
|
||||||
|
if (document.querySelector(".hamburger-icon")) {
|
||||||
|
document.querySelector(".hamburger-icon").classList.add("open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isElement = document.querySelectorAll("#navbar-nav > li.nav-item");
|
||||||
|
Array.from(isElement).forEach(function (item) {
|
||||||
|
item.addEventListener("click", menuItem.bind(this), false);
|
||||||
|
item.addEventListener("mouseover", menuItem.bind(this), false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function menuItem(e) {
|
||||||
|
if (e.target && e.target.matches("a.nav-link span")) {
|
||||||
|
if (elementInViewport(e.target.parentElement.nextElementSibling) == false) {
|
||||||
|
e.target.parentElement.nextElementSibling.classList.add("dropdown-custom-right");
|
||||||
|
e.target.parentElement.parentElement.parentElement.parentElement.classList.add("dropdown-custom-right");
|
||||||
|
var eleChild = e.target.parentElement.nextElementSibling;
|
||||||
|
Array.from(eleChild.querySelectorAll(".menu-dropdown")).forEach(function (item) {
|
||||||
|
item.classList.add("dropdown-custom-right");
|
||||||
|
});
|
||||||
|
} else if (elementInViewport(e.target.parentElement.nextElementSibling) == true) {
|
||||||
|
if (window.innerWidth >= 1848) {
|
||||||
|
var elements = document.getElementsByClassName("dropdown-custom-right");
|
||||||
|
while (elements.length > 0) {
|
||||||
|
elements[0].classList.remove("dropdown-custom-right");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.target && e.target.matches("a.nav-link")) {
|
||||||
|
if (elementInViewport(e.target.nextElementSibling) == false) {
|
||||||
|
e.target.nextElementSibling.classList.add("dropdown-custom-right");
|
||||||
|
e.target.parentElement.parentElement.parentElement.classList.add("dropdown-custom-right");
|
||||||
|
var eleChild = e.target.nextElementSibling;
|
||||||
|
Array.from(eleChild.querySelectorAll(".menu-dropdown")).forEach(function (item) {
|
||||||
|
item.classList.add("dropdown-custom-right");
|
||||||
|
});
|
||||||
|
} else if (elementInViewport(e.target.nextElementSibling) == true) {
|
||||||
|
if (window.innerWidth >= 1848) {
|
||||||
|
var elements = document.getElementsByClassName("dropdown-custom-right");
|
||||||
|
while (elements.length > 0) {
|
||||||
|
elements[0].classList.remove("dropdown-custom-right");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function initsAlert() {
|
function initsAlert() {
|
||||||
var alerts = document.querySelectorAll('.alert.alert-dismissible');
|
var alerts = document.querySelectorAll('.alert.alert-dismissible');
|
||||||
alerts.forEach(function (el) {
|
alerts.forEach(function (el) {
|
||||||
@ -67,4 +176,61 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", initLanguage);
|
document.addEventListener("DOMContentLoaded", initLanguage);
|
||||||
|
|
||||||
|
|
||||||
|
function toggleHamburgerMenu() {
|
||||||
|
var windowSize = document.documentElement.clientWidth;
|
||||||
|
|
||||||
|
if (windowSize > 767)
|
||||||
|
document.querySelector(".hamburger-icon").classList.toggle("open");
|
||||||
|
|
||||||
|
//For collapse horizontal menu
|
||||||
|
if (document.documentElement.getAttribute("data-layout") === "horizontal") {
|
||||||
|
document.body.classList.contains("menu") ? document.body.classList.remove("menu") : document.body.classList.add("menu");
|
||||||
|
}
|
||||||
|
|
||||||
|
//For collapse vertical menu
|
||||||
|
if (document.documentElement.getAttribute("data-layout") === "vertical") {
|
||||||
|
if (windowSize <= 1025 && windowSize > 767) {
|
||||||
|
document.body.classList.remove("vertical-sidebar-enable");
|
||||||
|
document.documentElement.getAttribute("data-sidebar-size") == "sm" ?
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", "") :
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", "sm");
|
||||||
|
} else if (windowSize > 1025) {
|
||||||
|
document.body.classList.remove("vertical-sidebar-enable");
|
||||||
|
document.documentElement.getAttribute("data-sidebar-size") == "lg" ?
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", "sm") :
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", "lg");
|
||||||
|
} else if (windowSize <= 767) {
|
||||||
|
document.body.classList.add("vertical-sidebar-enable");
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", "lg");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// semibox menu
|
||||||
|
if (document.documentElement.getAttribute("data-layout") === "semibox") {
|
||||||
|
if (windowSize > 767) {
|
||||||
|
// (document.querySelector(".hamburger-icon").classList.contains("open")) ? document.documentElement.setAttribute('data-sidebar-visibility', "show"): '';
|
||||||
|
if (document.documentElement.getAttribute('data-sidebar-visibility') == "show") {
|
||||||
|
document.documentElement.getAttribute("data-sidebar-size") == "lg" ?
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", "sm") :
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", "lg");
|
||||||
|
} else {
|
||||||
|
document.getElementById("sidebar-visibility-show").click();
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", document.documentElement.getAttribute("data-sidebar-size"));
|
||||||
|
}
|
||||||
|
} else if (windowSize <= 767) {
|
||||||
|
document.body.classList.add("vertical-sidebar-enable");
|
||||||
|
document.documentElement.setAttribute("data-sidebar-size", "lg");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Two column menu
|
||||||
|
if (document.documentElement.getAttribute("data-layout") == "twocolumn") {
|
||||||
|
document.body.classList.contains("twocolumn-panel") ?
|
||||||
|
document.body.classList.remove("twocolumn-panel") :
|
||||||
|
document.body.classList.add("twocolumn-panel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export default class PresupuestoWizard {
|
|||||||
paginasColor: 0,
|
paginasColor: 0,
|
||||||
posicionPaginasColor: '',
|
posicionPaginasColor: '',
|
||||||
tipoEncuadernacion: 'fresado',
|
tipoEncuadernacion: 'fresado',
|
||||||
entregaTipo: 'peninsula',
|
entregaTipo: 'peninsula',
|
||||||
ivaReducido: true,
|
ivaReducido: true,
|
||||||
},
|
},
|
||||||
interior: {
|
interior: {
|
||||||
@ -254,67 +254,32 @@ export default class PresupuestoWizard {
|
|||||||
if (this.opts.canSave) {
|
if (this.opts.canSave) {
|
||||||
$('.guardar-presupuesto').on('click', async () => {
|
$('.guardar-presupuesto').on('click', async () => {
|
||||||
|
|
||||||
const alert = $('#form-errors');
|
const success = await this.#guardarPresupuesto();
|
||||||
const servicios = [];
|
if (success) {
|
||||||
const payload = {
|
|
||||||
id: this.opts.presupuestoId,
|
|
||||||
mode: this.opts.mode,
|
|
||||||
presupuesto: this.#getPresupuestoData(),
|
|
||||||
servicios: this.formData.servicios.servicios,
|
|
||||||
datosMaquetacion: this.formData.servicios.datosMaquetacion,
|
|
||||||
datosMarcapaginas: this.formData.servicios.datosMarcapaginas,
|
|
||||||
cliente_id: $('#cliente_id').val() || null,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
alert.addClass('d-none').find('ul').empty();
|
|
||||||
$.ajax({
|
|
||||||
url: '/presupuesto/api/save',
|
|
||||||
type: 'POST',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify(payload)
|
|
||||||
}).then((data) => {
|
|
||||||
Swal.fire({
|
|
||||||
icon: 'success',
|
|
||||||
title: window.languageBundle?.get('presupuesto.exito.guardado') || 'Guardado',
|
|
||||||
timer: 1800,
|
|
||||||
buttonsStyling: false,
|
|
||||||
customClass: {
|
|
||||||
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
|
|
||||||
cancelButton: 'btn btn-light' // clases para cancelar
|
|
||||||
},
|
|
||||||
showConfirmButton: false
|
|
||||||
});
|
|
||||||
// opcional: actualizar window.PRESUPUESTO_ID/resumen con el id devuelto
|
|
||||||
if (data.id) this.opts.presupuestoId = data.id;
|
|
||||||
}).catch((xhr, status, error) => {
|
|
||||||
|
|
||||||
const errors = xhr.responseJSON;
|
|
||||||
if (errors && typeof errors === 'object') {
|
|
||||||
if (!this.DEBUG && xhr.responseJSON.error && xhr.responseJSON.error == 'Internal Server Error') {
|
|
||||||
console.error("Error al validar los datos generales. Internal Server Error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Object.values(errors).forEach(errorMsg => {
|
|
||||||
alert.find('ul').append(`<li>${errorMsg}</li>`);
|
|
||||||
});
|
|
||||||
alert.removeClass('d-none');
|
|
||||||
} else {
|
|
||||||
alert.find('ul').append('<li>Error desconocido. Por favor, inténtelo de nuevo más tarde.</li>');
|
|
||||||
$(window).scrollTop(0);
|
|
||||||
alert.removeClass('d-none');
|
|
||||||
}
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
icon: 'error',
|
icon: 'success',
|
||||||
title: window.languageBundle?.get('presupuesto.add.error.save.title') || 'Error',
|
title: window.languageBundle?.get('presupuesto.exito.guardado') || 'Guardado',
|
||||||
text: e?.message || '',
|
timer: 1800,
|
||||||
buttonsStyling: false,
|
buttonsStyling: false,
|
||||||
customClass: {
|
customClass: {
|
||||||
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
|
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
|
||||||
cancelButton: 'btn btn-light' // clases para cancelar
|
cancelButton: 'btn btn-light' // clases para cancelar
|
||||||
},
|
},
|
||||||
|
showConfirmButton: false
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.add-cart-btn').on('click', async () => {
|
||||||
|
const success = await this.#guardarPresupuesto();
|
||||||
|
if (success) {
|
||||||
|
await $.ajax({
|
||||||
|
url: `/cart/add/${this.opts.presupuestoId}`,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -322,6 +287,68 @@ export default class PresupuestoWizard {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #guardarPresupuesto() {
|
||||||
|
|
||||||
|
const alert = $('#form-errors');
|
||||||
|
const servicios = [];
|
||||||
|
const payload = {
|
||||||
|
id: this.opts.presupuestoId,
|
||||||
|
mode: this.opts.mode,
|
||||||
|
presupuesto: this.#getPresupuestoData(),
|
||||||
|
servicios: this.formData.servicios.servicios,
|
||||||
|
datosMaquetacion: this.formData.servicios.datosMaquetacion,
|
||||||
|
datosMarcapaginas: this.formData.servicios.datosMarcapaginas,
|
||||||
|
cliente_id: $('#cliente_id').val() || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
alert.addClass('d-none').find('ul').empty();
|
||||||
|
return await $.ajax({
|
||||||
|
url: '/presupuesto/api/save',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(payload)
|
||||||
|
}).then((data) => {
|
||||||
|
|
||||||
|
if (data.id) this.opts.presupuestoId = data.id;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).catch((xhr, status, error) => {
|
||||||
|
|
||||||
|
const errors = xhr.responseJSON;
|
||||||
|
if (errors && typeof errors === 'object') {
|
||||||
|
if (!this.DEBUG && xhr.responseJSON.error && xhr.responseJSON.error == 'Internal Server Error') {
|
||||||
|
console.error("Error al validar los datos generales. Internal Server Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.values(errors).forEach(errorMsg => {
|
||||||
|
alert.find('ul').append(`<li>${errorMsg}</li>`);
|
||||||
|
});
|
||||||
|
alert.removeClass('d-none');
|
||||||
|
} else {
|
||||||
|
alert.find('ul').append('<li>Error desconocido. Por favor, inténtelo de nuevo más tarde.</li>');
|
||||||
|
$(window).scrollTop(0);
|
||||||
|
alert.removeClass('d-none');
|
||||||
|
}
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
return false;
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: window.languageBundle?.get('presupuesto.add.error.save.title') || 'Error',
|
||||||
|
text: e?.message || '',
|
||||||
|
buttonsStyling: false,
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'btn btn-secondary me-2', // clases para el botón confirmar
|
||||||
|
cancelButton: 'btn btn-light' // clases para cancelar
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#cacheFormData() {
|
#cacheFormData() {
|
||||||
if (!this.opts.useSessionCache) return;
|
if (!this.opts.useSessionCache) return;
|
||||||
sessionStorage.setItem('formData', JSON.stringify(this.formData));
|
sessionStorage.setItem('formData', JSON.stringify(this.formData));
|
||||||
@ -1633,7 +1660,6 @@ export default class PresupuestoWizard {
|
|||||||
|
|
||||||
if ($target.prop('checked')) {
|
if ($target.prop('checked')) {
|
||||||
this.formData.servicios.servicios.push(
|
this.formData.servicios.servicios.push(
|
||||||
$target.val(),
|
|
||||||
{
|
{
|
||||||
id: $target.attr('id') ?? $(`label[for="${$target.attr('id')}"] .service-title`).text().trim(),
|
id: $target.attr('id') ?? $(`label[for="${$target.attr('id')}"] .service-title`).text().trim(),
|
||||||
label: $(`label[for="${$target.attr('id')}"] .service-title`).text().trim(),
|
label: $(`label[for="${$target.attr('id')}"] .service-title`).text().trim(),
|
||||||
@ -1675,6 +1701,14 @@ export default class PresupuestoWizard {
|
|||||||
this.divExtras.append(item.render());
|
this.divExtras.append(item.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!this.formData.servicios.servicios.includes(s => s.id === "ferro-digital")) {
|
||||||
|
this.formData.servicios.servicios.push({
|
||||||
|
id: "ferro-digital",
|
||||||
|
label: "Ferro Digital",
|
||||||
|
units: 1,
|
||||||
|
price: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
this.#cacheFormData();
|
this.#cacheFormData();
|
||||||
Summary.updateExtras();
|
Summary.updateExtras();
|
||||||
}
|
}
|
||||||
@ -1813,15 +1847,15 @@ export default class PresupuestoWizard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$('#resumen-base').text(formateaMoneda(data.base_imponible, 2, locale));
|
$('#resumen-base').text(formateaMoneda(data.base_imponible, 2, locale));
|
||||||
if(data.iva_importe_4 > 0) {
|
if (data.iva_importe_4 > 0) {
|
||||||
$('#tr-resumen-iva4').removeClass('d-none');
|
$('#tr-resumen-iva4').removeClass('d-none');
|
||||||
$('#resumen-iva4').text(formateaMoneda(data.iva_importe_4, 2, locale));
|
$('#resumen-iva4').text(formateaMoneda(data.iva_importe_4, 2, locale));
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
$('#tr-resumen-iva4').addClass('d-none');
|
$('#tr-resumen-iva4').addClass('d-none');
|
||||||
$('#resumen-iva4').text(formateaMoneda(0, 2, locale));
|
$('#resumen-iva4').text(formateaMoneda(0, 2, locale));
|
||||||
}
|
}
|
||||||
if(data.iva_importe_21 > 0) {
|
if (data.iva_importe_21 > 0) {
|
||||||
$('#tr-resumen-iva21').removeClass('d-none');
|
$('#tr-resumen-iva21').removeClass('d-none');
|
||||||
$('#resumen-iva21').text(formateaMoneda(data.iva_importe_21, 2, locale));
|
$('#resumen-iva21').text(formateaMoneda(data.iva_importe_21, 2, locale));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -0,0 +1,74 @@
|
|||||||
|
<!-- _cartItem.html -->
|
||||||
|
<div th:fragment="cartItem(item)" class="card product mb-3 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row gy-3">
|
||||||
|
|
||||||
|
<div class="col-sm-auto">
|
||||||
|
<div class="avatar-lg bg-light rounded p-1">
|
||||||
|
<img th:src="${item.imagen != null ? item.imagen : '/assets/images/products/placeholder.png'}"
|
||||||
|
alt="portada" class="img-fluid d-block rounded">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Detalles -->
|
||||||
|
<div class="col-sm">
|
||||||
|
<!-- Título / enlace -->
|
||||||
|
<h5 class="fs-18 text-truncate mb-1">
|
||||||
|
<a th:href="@{|presupuesto/edit/${item.presupuestoId}|}" class="text-dark"
|
||||||
|
th:text="${item.titulo != null ? item.titulo : 'Presupuesto #'}">
|
||||||
|
Presupuesto
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
<h5 class="fs-14 text-truncate mb-1">
|
||||||
|
<span th:text="#{cart.item.presupuesto-numero}">Presupuesto #</span>
|
||||||
|
<span th:text="${item.presupuestoId != null ? item.presupuestoId : ''}">#</span>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<!-- Detalles opcionales (ej: cliente, fecha, etc.) -->
|
||||||
|
<ul class="list-inline text-muted mb-2">
|
||||||
|
<div th:each="linea : ${item.lineas}">
|
||||||
|
<li class="list-inline-item me-3">
|
||||||
|
<div th:utext="${linea['descripcion']}"></div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Precio o totales (si los tienes) -->
|
||||||
|
<div class="col-sm-auto text-end">
|
||||||
|
<p class="text-muted mb-1">Precio estimado</p>
|
||||||
|
<h5 class="fs-14 mb-0">
|
||||||
|
<span th:text="${item.price != null ? #numbers.formatDecimal(item.price, 1, 2) : '-'}">0,00</span> €
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer bg-light">
|
||||||
|
<div class="row align-items-center gy-3">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="d-flex flex-wrap my-n1">
|
||||||
|
<!-- Botón eliminar -->
|
||||||
|
<div>
|
||||||
|
<form th:action="@{|/cart/${item.id}/remove|}" method="post" class="d-inline">
|
||||||
|
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||||
|
<button type="submit" class="btn btn-sm btn-link text-body p-1 px-2">
|
||||||
|
<i class="ri-delete-bin-fill text-muted align-bottom me-1"></i> Eliminar
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-auto text-end">
|
||||||
|
<div class="d-flex align-items-center gap-2 text-muted">
|
||||||
|
<div>Total:</div>
|
||||||
|
<h5 class="fs-14 mb-0">
|
||||||
|
<span
|
||||||
|
th:text="${item.price != null ? #numbers.formatDecimal(item.price, 1, 2) : '-'}">0,00</span>
|
||||||
|
€
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
55
src/main/resources/templates/imprimelibros/cart/cart.html
Normal file
55
src/main/resources/templates/imprimelibros/cart/cart.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
layout:decorate="~{imprimelibros/layout}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<th:block layout:fragment="pagetitle" />
|
||||||
|
<th:block th:replace="~{imprimelibros/partials/head-css :: head-css}" />
|
||||||
|
<th:block layout:fragment="pagecss">
|
||||||
|
<link th:href="@{/assets/libs/datatables/dataTables.bootstrap5.min.css}" rel="stylesheet" />
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="pagecss">
|
||||||
|
<link th:href="@{/assets/css/presupuestador.css}" rel="stylesheet" />
|
||||||
|
</th:block>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
|
||||||
|
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}"
|
||||||
|
sec:authorize="isAuthenticated() and hasAnyRole('SUPERADMIN','ADMIN')">
|
||||||
|
|
||||||
|
<th:block layout:fragment="content">
|
||||||
|
<div th:if="${#authorization.expression('isAuthenticated()')}">
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/"><i class="ri-home-5-fill"></i></a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page" th:text="#{cart.title}">Cesta de la
|
||||||
|
compra</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div th:if="${items.isEmpty()}">
|
||||||
|
<div class="alert alert-info" role="alert" th:text="#{cart.empty}"></div>
|
||||||
|
</div>
|
||||||
|
<div th:unless="${#lists.isEmpty(items)}">
|
||||||
|
|
||||||
|
<div th:each="item : ${items}" th:insert="~{imprimelibros/cart/_cartItem :: cartItem(${item})}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<th:block th:replace="~{theme/partials/vendor-scripts :: scripts}" />
|
||||||
|
<th:block layout:fragment="pagejs">
|
||||||
|
<script th:inline="javascript">
|
||||||
|
window.languageBundle = /*[[${languageBundle}]]*/ {};
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -68,7 +68,9 @@
|
|||||||
class="ms-1 header-item d-none d-sm-flex">
|
class="ms-1 header-item d-none d-sm-flex">
|
||||||
<button type="button" id="btn_cart"
|
<button type="button" id="btn_cart"
|
||||||
class="btn btn-icon btn-topbar material-shadow-none btn-ghost-secondary rounded-circle light-dark-mode">
|
class="btn btn-icon btn-topbar material-shadow-none btn-ghost-secondary rounded-circle light-dark-mode">
|
||||||
<i class="bx bx-cart fs-22"></i>
|
<a href="/cart">
|
||||||
|
<i class="ri-shopping-cart-2-line fs-22"></i>
|
||||||
|
</a>
|
||||||
<span id="cart-item-count"
|
<span id="cart-item-count"
|
||||||
class="position-absolute topbar-badge cartitem-badge fs-10 translate-middle badge rounded-pill bg-info d-none">
|
class="position-absolute topbar-badge cartitem-badge fs-10 translate-middle badge rounded-pill bg-info d-none">
|
||||||
0
|
0
|
||||||
|
|||||||
@ -7,6 +7,12 @@
|
|||||||
<span th:text="#{presupuesto.guardar}">Guardar</span>
|
<span th:text="#{presupuesto.guardar}">Guardar</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button th:if="${appMode == 'add' or appMode == 'edit'}" id="btn-add-cart" type="button"
|
||||||
|
class="btn btn-secondary d-flex align-items-center mx-2 add-cart-btn">
|
||||||
|
<i class="ri-shopping-cart-line me-2"></i>
|
||||||
|
<span th:text="#{presupuesto.add-to-cart}">Añadir al carrito</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button id="btn-imprimir" type="button"
|
<button id="btn-imprimir" type="button"
|
||||||
class="btn btn-secondary d-flex align-items-center mx-2 imprimir-presupuesto">
|
class="btn btn-secondary d-flex align-items-center mx-2 imprimir-presupuesto">
|
||||||
<i class="ri-printer-line me-2"></i>
|
<i class="ri-printer-line me-2"></i>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<table id="presupuestos-clientes-user-datatable" class="table table-striped table-nowrap responsive w-100">
|
<table id="presupuestos-clientes-user-datatable" class="table table-striped table-nowrap responsive w-100">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" th:text="#{presupuesto.tabla.id}">ID</th>
|
<th scope="col" th:text="#{presupuesto.tabla.numero}">Número</th>
|
||||||
<th scope="col" th:text="#{presupuesto.tabla.titulo}">Título</th>
|
<th scope="col" th:text="#{presupuesto.tabla.titulo}">Título</th>
|
||||||
<th scope="col" th:text="#{presupuesto.tabla.encuadernacion}">Encuadernación</th>
|
<th scope="col" th:text="#{presupuesto.tabla.encuadernacion}">Encuadernación</th>
|
||||||
<th scope="col" th:text="#{presupuesto.tabla.cubierta}">Cubierta</th>
|
<th scope="col" th:text="#{presupuesto.tabla.cubierta}">Cubierta</th>
|
||||||
|
|||||||
Reference in New Issue
Block a user