trabajando en el datatables de los presupuestos

This commit is contained in:
2025-10-06 15:32:50 +02:00
parent b2f3ef042e
commit 1e8f9cafb3
27 changed files with 513 additions and 36 deletions

View File

@ -13,7 +13,7 @@ import com.imprimelibros.erp.users.User;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditedSoftDeleteEntity {
public abstract class AbstractAuditedEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@ -16,7 +16,6 @@ import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;

View File

@ -48,6 +48,6 @@ public class HomeController {
Map<String, String> translations = Map.of();
model.addAttribute("languageBundle", translations);
}
return "imprimelibros/home";
return "imprimelibros/home/home";
}
}

View File

@ -9,19 +9,15 @@ import com.imprimelibros.erp.presupuesto.validation.PresupuestoValidationGroups;
import com.imprimelibros.erp.presupuesto.validation.Tamanio;
import com.imprimelibros.erp.common.HtmlStripConverter;
import com.imprimelibros.erp.common.jpa.AbstractAuditedSoftDeleteEntity;
import com.imprimelibros.erp.common.jpa.AbstractAuditedEntity;
import jakarta.persistence.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.math.BigDecimal;
import java.time.Instant;
import com.imprimelibros.erp.users.User;
@ -38,7 +34,7 @@ import com.imprimelibros.erp.users.User;
})
@SQLDelete(sql = "UPDATE presupuesto SET deleted = 1, deleted_at = NOW(3) WHERE id = ?")
@SQLRestriction("deleted = 0")
public class Presupuesto extends AbstractAuditedSoftDeleteEntity implements Cloneable {
public class Presupuesto extends AbstractAuditedEntity implements Cloneable {
public enum TipoEncuadernacion {
fresado("presupuesto.fresado"),

View File

@ -3,18 +3,23 @@ package com.imprimelibros.erp.presupuesto;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.validation.BindingResult;
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.ResponseBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@ -23,7 +28,15 @@ import org.springframework.http.MediaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.imprimelibros.erp.configuracion.margenes_presupuestos.MargenPresupuesto;
import com.imprimelibros.erp.datatables.DataTable;
import com.imprimelibros.erp.datatables.DataTablesParser;
import com.imprimelibros.erp.datatables.DataTablesRequest;
import com.imprimelibros.erp.datatables.DataTablesResponse;
import com.imprimelibros.erp.externalApi.skApiClient;
import com.imprimelibros.erp.i18n.TranslationService;
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoCubierta;
import com.imprimelibros.erp.presupuesto.Presupuesto.TipoEncuadernacion;
import com.imprimelibros.erp.presupuesto.classes.ImagenPresupuesto;
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMaquetacion;
import com.imprimelibros.erp.presupuesto.classes.PresupuestoMarcapaginas;
@ -46,9 +59,14 @@ public class PresupuestoController {
protected MessageSource messageSource;
private final ObjectMapper objectMapper;
private final TranslationService translationService;
private final PresupuestoRepository repo;
public PresupuestoController(ObjectMapper objectMapper) {
public PresupuestoController(ObjectMapper objectMapper, TranslationService translationService,
PresupuestoRepository repo) {
this.objectMapper = objectMapper;
this.translationService = translationService;
this.repo = repo;
}
@PostMapping("/public/validar/datos-generales")
@ -407,4 +425,68 @@ public class PresupuestoController {
return ResponseEntity.ok(resumen);
}
// =============================================
// MÉTODOS PARA USUARIOS AUTENTICADOS
// =============================================
@GetMapping
public String getPresupuestoView(Model model, Authentication authentication, Locale locale) {
List<String> keys = List.of();
Map<String, String> translations = translationService.getTranslations(locale, keys);
model.addAttribute("languageBundle", translations);
return "imprimelibros/presupuestos/presupuesto-list";
}
@GetMapping(value = "/datatable/{tipo}", produces = "application/json")
@ResponseBody
public DataTablesResponse<Map<String, Object>> datatable(@RequestParam("tipo") String tipo,
HttpServletRequest request, Authentication authentication,
Locale locale) {
DataTablesRequest dt = DataTablesParser.from(request);
List<String> searchable = List.of(
"id",
"titulo",
"cliente",
"tipoEncuadernacion",
"tipoCubierta",
"tipoImpresion",
"tirada",
"paginas",
"estado", "total", "pais", "region", "ciudad", "updatedAt");
List<String> orderable = List.of(/*
* "id",
* "tipoEncuadernacion",
* "tipoCubierta",
* "tiradaMin",
* "tiradaMax",
* "margenMin",
* "margenMax"
*/);
Specification<MargenPresupuesto> base = (root, query, cb) -> cb.conjunction();
long total = repo.count();
return DataTable
.of(repo, MargenPresupuesto.class, dt, searchable) // 'searchable' en DataTable.java
// edita columnas "reales":
.orderable(orderable)
.add("actions", (presupuesto) -> {
return "<div class=\"hstack gap-3 flex-wrap\">\n" +
" <a href=\"javascript:void(0);\" data-id=\"" + presupuesto.getId()
+ "\" class=\"link-success btn-edit-" + tipo
+ " fs-15\"><i class=\"ri-edit-2-line\"></i></a>\n"
+ " <a href=\"javascript:void(0);\" data-id=\"" + presupuesto.getId()
+ "\" class=\"link-danger btn-delete-" + tipo
+ " fs-15\"><i class=\"ri-delete-bin-5-line\"></i></a>\n"
+ " </div>";
})
.where(base)
.toJson(total);
}
}

View File

@ -1,14 +1,17 @@
package com.imprimelibros.erp.presupuesto;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.imprimelibros.erp.configuracion.margenes_presupuestos.MargenPresupuesto;
import java.util.*;
@Repository
public interface PresupuestoRepository extends JpaRepository<Presupuesto, Long> {
public interface PresupuestoRepository extends JpaRepository<Presupuesto, Long>, JpaSpecificationExecutor<MargenPresupuesto> {
Optional<Presupuesto> findFirstBySessionIdAndOrigenAndEstadoInOrderByUpdatedAtDesc(
String sessionId,

View File

@ -14,5 +14,6 @@ app.mensajes=Mensajes
app.logout=Cerrar sesión
app.sidebar.inicio=Inicio
app.sidebar.usuarios=Usuarios
app.sidebar.configuracion=Configuración
app.sidebar.presupuestos=Presupuestos
app.sidebar.configuracion=Configuración
app.sidebar.usuarios=Usuarios

View File

@ -1,3 +1,4 @@
presupuesto.title=Presupuestos
presupuesto.datos-generales=Datos Generales
presupuesto.interior=Interior
presupuesto.cubierta=Cubierta
@ -6,6 +7,26 @@ presupuesto.extras=Extras
presupuesto.resumen=Resumen
presupuesto.add-to-presupuesto=Añadir al presupuesto
presupuesto.calcular=Calcular
presupuesto.add=Añadir presupuesto
presupuesto.nav.presupuestos-cliente=Presupuestos cliente
presupuesto.nav.presupuestos-anonimos=Presupuestos anónimos
presupuesto.tabla.id=ID
presupuesto.tabla.titulo=Título
presupuesto.tabla.cliente=Cliente
presupuesto.tabla.encuadernacion=Encuadernación
presupuesto.tabla.cubierta=Cubierta
presupuesto.tabla.tipo-impresion=Tipo de impresión
presupuesto.tabla.tirada=Tirada
presupuesto.tabla.paginas=Páginas
presupuesto.tabla.estado=Estado
presupuesto.tabla.total-iva=Total (con IVA)
presupuesto.tabla.updated-at=Actualizado el
presupuesto.tabla.pais=País
presupuesto.tabla.region=Región
presupuesto.tabla.ciudad=Ciudad
presupuesto.tabla.acciones=Acciones
# Pestaña datos generales de presupuesto
presupuesto.informacion-libro=Información del libro

View File

@ -928,7 +928,7 @@ File: Main Css File
font-family: "Public Sans", sans-serif;
}
.navbar-menu .navbar-nav .nav-sm .nav-link:before {
content: "";
content: none;
width: 6px;
height: 1.5px;
background-color: var(--vz-vertical-menu-sub-item-color);
@ -6714,6 +6714,38 @@ a {
border-left-color: #ff7f5d;
}
/* ---- NAV SECUNDARIO CON PESTAÑITA OUTLINE ---- */
/* Solo la pestaña activa: estilo outline + pestañita */
.nav-secondary-outline.arrow-navtabs .nav-link.active {
color: #ff7f5d;
background: transparent;
border: 1px solid #ff7f5d; /* como el botón Iniciar sesión */
border-radius: .5rem;
position: relative;
}
/* Si el tema ya dibuja una flecha sólida, la anulamos */
.nav-secondary-outline.arrow-navtabs .nav-link.active::before {
display: none !important;
}
/* Pestañita tipo outline */
.nav-secondary-outline.arrow-navtabs .nav-link.active::after {
content: "";
position: absolute;
left: 50%;
bottom: -7px; /* ajusta si lo necesitas */
width: 12px;
height: 12px;
transform: translateX(-50%) rotate(225deg);
background: var(--vz-body-bg, var(--bs-body-bg, #fff)); /* color del fondo de la página */
border-left: 1px solid #ff7f5d;
border-top: 1px solid #ff7f5d;
z-index: 2;
}
.nav-success .nav-link.active {
color: #fff;
background-color: #3cd188;
@ -16329,4 +16361,5 @@ span.flatpickr-weekday {
position: absolute;
bottom: -18px;
left: -35px;
}
}

View File

@ -26,6 +26,22 @@
pageLength: 50,
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
responsive: true,
dom: 'Bfrtip',
buttons: {
dom: {
button: {
className: 'btn btn-sm btn-outline-primary me-1'
},
buttons: [
{ extend: 'copy' },
{ extend: 'csv' },
{ extend: 'excel' },
{ extend: 'pdf' },
{ extend: 'print' },
{ extend: 'colvis' }
],
}
},
ajax: {
url: '/configuracion/margenes-presupuesto/datatable',
method: 'GET',

View File

@ -216,6 +216,7 @@ class PresupuestoCliente {
if (tabButton) {
bootstrap.Tab.getOrCreateInstance(tabButton).show();
}
window.scrollTo({ top: 0, behavior: 'smooth' });
}
#getPresupuestoData() {

View File

@ -0,0 +1,75 @@
(() => {
// 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 table_anonimos = new DataTable('#presupuestos-anonimos-datatable', {
processing: true,
serverSide: true,
orderCellsTop: true,
stateSave: true,
pageLength: 50,
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
responsive: true,
dom: 'Bfrtip',
buttons: {
dom: {
button: {
className: 'btn btn-sm btn-outline-primary me-1'
},
buttons: [
{ extend: 'copy' },
{ extend: 'csv' },
{ extend: 'excel' },
{ extend: 'pdf' },
{ extend: 'print' },
{ extend: 'colvis' }
],
}
},
ajax: {
url: '/presupuesto/datatable/anonimos',
method: 'GET',
data: function (d) {
d.f_encuadernacion = $('#search-encuadernacion').val() || ''; // 'USER' | 'ADMIN' | 'SUPERADMIN' | ''
d.f_cubierta = $('#search-cubierta').val() || ''; // 'true' | 'false' | ''
}
},
order: [[0, 'asc']],
columns: [
{ data: 'id', name: 'id', orderable: true },
{ data: 'cliente', name: 'cliente', orderable: true },
{ data: 'tipoEncuadernacion', name: 'tipoEncuadernacion', orderable: true },
{ data: 'tipoCubierta', name: 'tipoCubierta', orderable: true },
{ data: 'tipoImpresion', name: 'tipoImpresion', orderable: true },
{ data: 'tirada', name: 'tirada', orderable: true },
{ data: 'paginas', name: 'paginas', orderable: true },
{ data: 'estado', name: 'estado', orderable: true },
{ data: 'total', name: 'total', orderable: true },
{ data: 'pais', name: 'pais', orderable: true },
{ data: 'region', name: 'region', orderable: true },
{ data: 'ciudad', name: 'ciudad', orderable: true },
{ data: 'updatedAt', name: 'updatedAt', orderable: true },
{ data: 'actions', name: 'actions' }
],
columnDefs: [{ targets: -1, orderable: false, searchable: false }]
});
})();

View File

@ -19,6 +19,22 @@ $(() => {
pageLength: 50,
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
responsive: true,
dom: 'Bfrtip',
buttons: {
dom: {
button: {
className: 'btn btn-sm btn-outline-primary me-1'
},
buttons: [
{ extend: 'copy' },
{ extend: 'csv' },
{ extend: 'excel' },
{ extend: 'pdf' },
{ extend: 'print' },
{ extend: 'colvis' }
],
}
},
ajax: {
url: '/users/datatable',
method: 'GET',

View File

@ -0,0 +1,5 @@
/*!
* Column visibility buttons for Buttons and DataTables.
* © SpryMedia Ltd - datatables.net/license
*/
!function(i){var o,e;"function"==typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(n){return i(n,window,document)}):"object"==typeof exports?(o=require("jquery"),e=function(n,t){t.fn.dataTable||require("datatables.net")(n,t),t.fn.dataTable.Buttons||require("datatables.net-buttons")(n,t)},"undefined"==typeof window?module.exports=function(n,t){return n=n||window,t=t||o(n),e(n,t),i(t,0,n.document)}:(e(window,o),module.exports=i(o,window,window.document))):i(jQuery,window,document)}(function(n,t,i){"use strict";var e=n.fn.dataTable;return n.extend(e.ext.buttons,{colvis:function(n,t){var i=null,o={extend:"collection",init:function(n,t){i=t},text:function(n){return n.i18n("buttons.colvis","Column visibility")},className:"buttons-colvis",closeButton:!1,buttons:[{extend:"columnsToggle",columns:t.columns,columnText:t.columnText}]};return n.on("column-reorder.dt"+t.namespace,function(){n.button(null,n.button(null,i).node()).collectionRebuild([{extend:"columnsToggle",columns:t.columns,columnText:t.columnText}])}),o},columnsToggle:function(n,t){return n.columns(t.columns).indexes().map(function(n){return{extend:"columnToggle",columns:n,columnText:t.columnText}}).toArray()},columnToggle:function(n,t){return{extend:"columnVisibility",columns:t.columns,columnText:t.columnText}},columnsVisibility:function(n,t){return n.columns(t.columns).indexes().map(function(n){return{extend:"columnVisibility",columns:n,visibility:t.visibility,columnText:t.columnText}}).toArray()},columnVisibility:{columns:void 0,text:function(n,t,i){return i._columnText(n,i)},className:"buttons-columnVisibility",action:function(n,t,i,o){var t=t.columns(o.columns),e=t.visible();t.visible(void 0!==o.visibility?o.visibility:!(e.length&&e[0]))},init:function(e,n,t){var u=this,l=e.column(t.columns);n.attr("data-cv-idx",t.columns),e.on("column-visibility.dt"+t.namespace,function(n,t,i,o){l.index()!==i||t.bDestroying||t.nTable!=e.settings()[0].nTable||u.active(o)}).on("column-reorder.dt"+t.namespace,function(){t.destroying||1===e.columns(t.columns).count()&&(l=e.column(t.columns),u.text(t._columnText(e,t)),u.active(l.visible()))}),this.active(l.visible())},destroy:function(n,t,i){n.off("column-visibility.dt"+i.namespace).off("column-reorder.dt"+i.namespace)},_columnText:function(n,t){var i,o;return"string"==typeof t.text?t.text:(o=n.column(t.columns).title(),i=n.column(t.columns).index(),o=o.replace(/\n/g," ").replace(/<br\s*\/?>/gi," ").replace(/<select(.*?)<\/select\s*>/gi,""),o=e.Buttons.stripHtmlComments(o),o=e.util.stripHtml(o).trim(),t.columnText?t.columnText(n,i,o):o)}},colvisRestore:{className:"buttons-colvisRestore",text:function(n){return n.i18n("buttons.colvisRestore","Restore visibility")},init:function(n,t,i){n.columns().every(function(){var n=this.init();void 0===n.__visOriginal&&(n.__visOriginal=this.visible())})},action:function(n,t,i,o){t.columns().every(function(n){var t=this.init();this.visible(t.__visOriginal)})}},colvisGroup:{className:"buttons-colvisGroup",action:function(n,t,i,o){t.columns(o.show).visible(!0,!1),t.columns(o.hide).visible(!1,!1),t.columns.adjust()},show:[],hide:[]}}),e});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
/*!
* Print button for Buttons and DataTables.
* © SpryMedia Ltd - datatables.net/license
*/
!function(n){var o,r;"function"==typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(t){return n(t,window,document)}):"object"==typeof exports?(o=require("jquery"),r=function(t,e){e.fn.dataTable||require("datatables.net")(t,e),e.fn.dataTable.Buttons||require("datatables.net-buttons")(t,e)},"undefined"==typeof window?module.exports=function(t,e){return t=t||window,e=e||o(t),r(t,e),n(e,t,t.document)}:(r(window,o),module.exports=n(o,window,window.document))):n(jQuery,window,document)}(function(p,f,t){"use strict";function m(t){return n.href=t,-1===(t=n.host).indexOf("/")&&0!==n.pathname.indexOf("/")&&(t+="/"),n.protocol+"//"+t+n.pathname+n.search}var e=p.fn.dataTable,n=t.createElement("a");return e.ext.buttons.print={className:"buttons-print",text:function(t){return t.i18n("buttons.print","Print")},action:function(t,e,n,o,r){var a=e.buttons.exportData(p.extend({decodeEntities:!1},o.exportOptions)),i=e.buttons.exportInfo(o),s=e.columns(o.exportOptions.columns).nodes().map(function(t){return t.className}).toArray(),u='<table class="'+e.table().node().className+'">';o.header&&(u+="<thead>"+a.headerStructure.map(function(t){return"<tr>"+t.map(function(t){return t?'<th colspan="'+t.colspan+'" rowspan="'+t.rowspan+'">'+t.title+"</th>":""}).join("")+"</tr>"}).join("")+"</thead>"),u+="<tbody>";for(var c=0,d=a.body.length;c<d;c++)u+=function(t,e){for(var n="<tr>",o=0,r=t.length;o<r;o++){var a=null===t[o]||void 0===t[o]?"":t[o];n+="<"+e+" "+(s[o]?'class="'+s[o]+'"':"")+">"+a+"</"+e+">"}return n+"</tr>"}(a.body[c],"td");u+="</tbody>",o.footer&&a.footer&&(u+="<tfoot>"+a.footerStructure.map(function(t){return"<tr>"+t.map(function(t){return t?'<th colspan="'+t.colspan+'" rowspan="'+t.rowspan+'">'+t.title+"</th>":""}).join("")+"</tr>"}).join("")+"</tfoot>"),u+="</table>";var l=f.open("","");l?(l.document.close(),l.document.title=i.title,p('style, link[rel="stylesheet"]').each(function(){var t=this.cloneNode(!0);"link"===t.tagName.toLowerCase()&&(t.href=m(t.href)),l.document.head.appendChild(t)}),o.customScripts&&o.customScripts.forEach(function(t){var e=l.document.createElement("script");e.src=t,l.document.getElementsByTagName("head")[0].appendChild(e)}),l.document.body.innerHTML="<h1>"+i.title+"</h1><div>"+(i.messageTop||"")+"</div>"+u+"<div>"+(i.messageBottom||"")+"</div>",p(l.document.body).addClass("dt-print-view"),p("img",l.document.body).each(function(t,e){e.setAttribute("src",m(e.getAttribute("src")))}),o.customize&&o.customize(l,o,e),l.setTimeout(function(){o.autoPrint&&(l.print(),l.close())},1e3),r()):e.buttons.info(e.i18n("buttons.printErrorTitle","Unable to open print view"),e.i18n("buttons.printErrorMsg","Please allow popups in your browser for this site to be able to view the print view."),5e3)},async:100,title:"*",messageTop:"*",messageBottom:"*",exportOptions:{},header:!0,footer:!0,autoPrint:!0,customize:null},e});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -29,21 +29,24 @@
<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="#{margenes-presupuesto.breadcrumb}">Márgenes de presupuesto</li>
<li class="breadcrumb-item active" aria-current="page" th:text="#{margenes-presupuesto.breadcrumb}">
Márgenes de presupuesto</li>
</ol>
</nav>
<div class="container-fluid">
<button type="button" class="btn btn-secondary mb-3" id="addButton">
<i class="ri-add-line align-bottom me-1"></i> <span th:text="#{margenes-presupuesto.add}">Añadir</span>
<i class="ri-add-line align-bottom me-1"></i> <span
th:text="#{margenes-presupuesto.add}">Añadir</span>
</button>
<table id="margenes-datatable" class="table table-striped table-nowrap responsive w-100">
<thead>
<tr>
<th scope="col" th:text="#{margenes-presupuesto.tabla.id}">ID</th>
<th scope="col" th:text="#{margenes-presupuesto.tabla.tipo_encuadernacion}">Tipo encuadernación</th>
<th scope="col" th:text="#{margenes-presupuesto.tabla.tipo_encuadernacion}">Tipo
encuadernación</th>
<th scope="col" th:text="#{margenes-presupuesto.tabla.tipo_cubierta}">Tipo cubierta</th>
<th scope="col" th:text="#{margenes-presupuesto.tabla.tirada_minima}">Tirada Mín.</th>
<th scope="col" th:text="#{margenes-presupuesto.tabla.tirada_maxima}">Tirada Máx.</th>
@ -52,9 +55,11 @@
<th scope="col" th:text="#{margenes-presupuesto.tabla.acciones}">Acciones</th>
</tr>
<tr>
<th><input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="id" /></th>
<th><input type="text" class="form-control form-control-sm margenes-presupuesto-filter"
data-col="id" /></th>
<th>
<select class="form-select form-select-sm margenes-presupuesto-select-filter" id="search-encuadernacion">
<select class="form-select form-select-sm margenes-presupuesto-select-filter"
id="search-encuadernacion">
<option value="" th:text="#{margenes-presupuesto.todos}">Todos</option>
<option value="fresado" th:text="#{presupuesto.fresado}">Fresado</option>
<option value="cosido" th:text="#{presupuesto.cosido}">Cosido</option>
@ -64,24 +69,30 @@
</select>
</th>
<th>
<select class="form-select form-select-sm margenes-presupuesto-select-filter" id="search-cubierta">
<select class="form-select form-select-sm margenes-presupuesto-select-filter"
id="search-cubierta">
<option value="" th:text="#{margenes-presupuesto.todos}">Todos</option>
<option value="tapaBlanda" th:text="#{presupuesto.tapa-blanda}"></option>
<option value="tapaDura" th:text="#{presupuesto.tapa-dura}"></option>
<option value="tapaDuraLomoRedondo" th:text="#{presupuesto.tapa-dura-lomo-redondo}"></option>
<option value="tapaDuraLomoRedondo" th:text="#{presupuesto.tapa-dura-lomo-redondo}">
</option>
</select>
</th>
<th>
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="tiradaMin" />
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter"
data-col="tiradaMin" />
</th>
<th>
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="tiradaMax" />
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter"
data-col="tiradaMax" />
</th>
<th>
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="margenMax" />
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter"
data-col="margenMax" />
</th>
<th>
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter" data-col="margenMin" />
<input type="text" class="form-control form-control-sm margenes-presupuesto-filter"
data-col="margenMin" />
</th>
<th></th>
</tr>
@ -101,7 +112,18 @@
</script>
<script th:src="@{/assets/libs/datatables/datatables.min.js}"></script>
<script th:src="@{/assets/libs/datatables/dataTables.bootstrap5.min.js}"></script>
<!-- JS de Buttons y dependencias -->
<script th:src="@{/assets/libs/datatables/dataTables.buttons.min.js}"></script>
<script th:src="@{/assets/libs/jszip/jszip.min.js}"></script>
<script th:src="@{/assets/libs/pdfmake/pdfmake.min.js}"></script>
<script th:src="@{/assets/libs/pdfmake/vfs_fonts.min.js}"></script>
<script th:src="@{/assets/libs/datatables/buttons.html5.min.js}"></script>
<script th:src="@{/assets/libs/datatables/buttons.print.min.js}"></script>
<script th:src="@{/assets/libs/datatables/buttons.colVis.min.js}"></script>
<script th:src="@{/assets/js/pages/imprimelibros/configuracion/margenes-presupuesto/list.js}"></script>
</th:block>
</body>

View File

@ -22,8 +22,7 @@
</span>
</a>
<button type="button" class="btn btn-sm p-0 fs-20 header-item float-end btn-vertical-sm-hover"
id="vertical-hover"
href="/#" data-bs-toggle="tooltip" data-bs-placement="right" title="Expand">
id="vertical-hover" href="/#" data-bs-toggle="tooltip" data-bs-placement="right" title="Expand">
<i class="ri-record-circle-line"></i>
</button>
</div>
@ -40,23 +39,35 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link menu-link" href="/users">
<i class="ri-user-line"></i> <span th:text="#{app.sidebar.usuarios}">Usuarios</span>
<a class="nav-link menu-link" href="/presupuesto">
<i class="ri-file-paper-2-line"></i> <span th:text="#{app.sidebar.presupuestos}">Presupuestos</span>
</a>
</li>
<div th:if="${#authentication.principal.role == 'SUPERADMIN'}">
<li class="nav-item">
<a class="nav-link menu-link collapsed" href="#sidebarConfig" data-bs-toggle="collapse" role="button" aria-expanded="false" aria-controls="sidebarConfig">
<i class="ri-settings-2-line"></i> <span th:text="#{app.sidebar.configuracion}">Configuración</span>
</a>
<a class="nav-link menu-link collapsed" href="#sidebarConfig" data-bs-toggle="collapse"
role="button" aria-expanded="false" aria-controls="sidebarConfig">
<i class="ri-settings-2-line"></i> <span
th:text="#{app.sidebar.configuracion}">Configuración</span>
</a>
<div class="collapse menu-dropdown" id="sidebarConfig">
<ul class="nav nav-sm flex-column">
<li class="nav-item">
<a href="/configuracion/margenes-presupuesto" class="nav-link" th:text="#{margenes-presupuesto.titulo}">Márgenes de presupuesto</a>
<a class="nav-link menu-link" href="/users">
<i class="ri-user-line"></i> <span
th:text="#{app.sidebar.usuarios}">Usuarios</span>
</a>
</li>
<div th:if="${#authentication.principal.role == 'SUPERADMIN'}">
<li class="nav-item">
<a href="/configuracion/margenes-presupuesto" class="nav-link">
<i class="ri-discount-percent-line"></i>
<span th:text="#{margenes-presupuesto.titulo}">Márgenes de presupuesto</span>
</a>
</li>
</div>
</ul>
</li>
</div>
</ul>
</div>
<!-- Sidebar -->

View File

@ -0,0 +1,28 @@
<div th:fragment="tabla-anonimos">
<table id="presupuesto-anonimos-datatable" class="table table-striped table-nowrap responsive w-100">
<thead>
<tr>
<th scope="col" th:text="#{presupuesto.tabla.id}">ID</th>
<th scope="col" th:text="#{presupuesto.tabla.titulo}">Título</th>
<th scope="col" th:text="#{presupuesto.tabla.cliente}">Cliente</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.tipo-impresion}">Tipo de impresión</th>
<th scope="col" th:text="#{presupuesto.tabla.tirada}">Tirada</th>
<th scope="col" th:text="#{presupuesto.tabla.paginas}">Páginas</th>
<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.pais}">País</th>
<th scope="col" th:text="#{presupuesto.tabla.region}">Región</th>
<th scope="col" th:text="#{presupuesto.tabla.ciudad}">Ciudad</th>
<th scope="col" th:text="#{presupuesto.tabla.updated-at}">Actualizado el</th>
<th scope="col" th:text="#{presupuesto.tabla.acciones}">Acciones</th>
</tr>
<tr>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>

View File

@ -0,0 +1,25 @@
<div th:fragment="tabla-cliente">
<table id="presupuesto-clientes-datatable" class="table table-striped table-nowrap responsive w-100">
<thead>
<tr>
<th scope="col" th:text="#{presupuesto.tabla.id}">ID</th>
<th scope="col" th:text="#{presupuesto.tabla.titulo}">Título</th>
<th scope="col" th:text="#{presupuesto.tabla.cliente}">Cliente</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.tipo-impresion}">Tipo de impresión</th>
<th scope="col" th:text="#{presupuesto.tabla.tirada}">Tirada</th>
<th scope="col" th:text="#{presupuesto.tabla.paginas}">Páginas</th>
<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>
</tr>
<tr>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>

View File

@ -0,0 +1,99 @@
<!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>
</head>
<body>
<div th:replace="~{imprimelibros/partials/topbar :: topbar}" />
<div th:replace="~{imprimelibros/partials/sidebar :: sidebar}"
th:unless="${#authorization.expression('isAuthenticated()') and (#authorization.expression('hasRole('SUPERADMIN', 'ADMIN')'))}" />
<th:block layout:fragment="content">
<div th:if="${#authorization.expression('isAuthenticated()')}">
<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="#{presupuesto.title}">Presupuestos
</li>
</ol>
</nav>
<div class="container-fluid">
<button type="button" class="btn btn-secondary mb-3" id="addPresupuestoButton">
<i class="ri-add-line align-bottom me-1"></i> <span th:text="#{presupuesto.add}">Añadir
presupuesto</span>
</button>
<ul class="nav nav-pills arrow-navtabs nav-secondary-outline bg-light mb-3" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" data-bs-toggle="tab" href="#arrow-presupuestos-cliente" role="tab"
aria-selected="true">
<span class="d-block d-sm-none"><i class="mdi mdi-home-variant"></i></span>
<span class="d-none d-sm-block"
th:text="#{presupuesto.nav.presupuestos-cliente}">Presupuestos cliente</span>
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#arrow-presupuestos-anonimos" role="tab"
aria-selected="false" tabindex="-1">
<span class="d-block d-sm-none"><i class="mdi mdi-account"></i></span>
<span class="d-none d-sm-block"
th:text="#{presupuesto.nav.presupuestos-anonimos}">Presupuestos anónimos</span>
</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content text-muted">
<div class="tab-pane active show" id="arrow-presupuestos-cliente" role="tabpanel">
<div
th:insert="~{imprimelibros/presupuestos/presupuesto-list-items/tabla-cliente :: tabla-cliente}">
</div>
</div>
<div class="tab-pane" id="arrow-presupuestos-anonimos" role="tabpanel">
<div
th:insert="~{imprimelibros/presupuestos/presupuesto-list-items/tabla-anonimos :: tabla-anonimos}">
</div>
</div>
</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>
<script th:src="@{/assets/libs/datatables/datatables.min.js}"></script>
<script th:src="@{/assets/libs/datatables/dataTables.bootstrap5.min.js}"></script>
<!-- JS de Buttons y dependencias -->
<script th:src="@{/assets/libs/datatables/dataTables.buttons.min.js}"></script>
<script th:src="@{/assets/libs/jszip/jszip.min.js}"></script>
<script th:src="@{/assets/libs/pdfmake/pdfmake.min.js}"></script>
<script th:src="@{/assets/libs/pdfmake/vfs_fonts.min.js}"></script>
<script th:src="@{/assets/libs/datatables/buttons.html5.min.js}"></script>
<script th:src="@{/assets/libs/datatables/buttons.print.min.js}"></script>
<script th:src="@{/assets/libs/datatables/buttons.colVis.min.js}"></script>
<script th:src="@{/assets/js/pages/imprimelibros/presupuestos/list.js}"></script>
</th:block>
</body>
</html>

View File

@ -90,6 +90,16 @@
</script>
<script th:src="@{/assets/libs/datatables/datatables.min.js}"></script>
<script th:src="@{/assets/libs/datatables/dataTables.bootstrap5.min.js}"></script>
<!-- JS de Buttons y dependencias -->
<script th:src="@{/assets/libs/datatables/dataTables.buttons.min.js}"></script>
<script th:src="@{/assets/libs/jszip/jszip.min.js}"></script>
<script th:src="@{/assets/libs/pdfmake/pdfmake.min.js}"></script>
<script th:src="@{/assets/libs/pdfmake/vfs_fonts.min.js}"></script>
<script th:src="@{/assets/libs/datatables/buttons.html5.min.js}"></script>
<script th:src="@{/assets/libs/datatables/buttons.print.min.js}"></script>
<script th:src="@{/assets/libs/datatables/buttons.colVis.min.js}"></script>
<script th:src="@{/assets/js/pages/imprimelibros/users/list.js}"></script>
</th:block>
</body>