mirror of
https://git.imnavajas.es/jjimenez/printhub.git
synced 2026-01-12 16:38:46 +00:00
subidos los ficheros nuevos
This commit is contained in:
42
src/main/java/com/printhub/printhub/model/Breadcrum.java
Normal file
42
src/main/java/com/printhub/printhub/model/Breadcrum.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package com.printhub.printhub.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Breadcrum {
|
||||||
|
|
||||||
|
public List<BreadcrumbItem> breadcrumb = new ArrayList<>();
|
||||||
|
|
||||||
|
public void addItem(String label, String dataKey, String url) {
|
||||||
|
BreadcrumbItem item = new BreadcrumbItem(label, url, dataKey);
|
||||||
|
breadcrumb.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BreadcrumbItem> getBreadcrumb() {
|
||||||
|
return breadcrumb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BreadcrumbItem {
|
||||||
|
private String label;
|
||||||
|
private String url;
|
||||||
|
private String dataKey;
|
||||||
|
|
||||||
|
public BreadcrumbItem(String label, String url, String dataKey) {
|
||||||
|
this.label = label;
|
||||||
|
this.url = url;
|
||||||
|
this.dataKey = dataKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDataKey() {
|
||||||
|
return dataKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.printhub.printhub.model.configuration;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "printers")
|
||||||
|
public class Printer {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
// --- Getters y Setters ---
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.printhub.printhub.repository.configuration;
|
||||||
|
|
||||||
|
import com.printhub.printhub.model.configuration.Printer;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface PrinterRepository extends JpaRepository<Printer, Long>, JpaSpecificationExecutor<Printer> {
|
||||||
|
|
||||||
|
// Consulta simple adicional (opcional)
|
||||||
|
List<Printer> findByNameContainingIgnoreCase(String name);
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
package com.printhub.printhub.service.configuration;
|
||||||
|
|
||||||
|
import com.printhub.printhub.model.configuration.Printer;
|
||||||
|
import com.printhub.printhub.repository.configuration.PrinterRepository;
|
||||||
|
import com.printhub.printhub.specification.configuration.PrinterSpecification;
|
||||||
|
import com.printhub.printhub.utils.datatables.DataTableBuilder;
|
||||||
|
import com.printhub.printhub.utils.datatables.DataTableRequest;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.*;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PrinterService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PrinterRepository printerRepository;
|
||||||
|
|
||||||
|
public Map<String, Object> listDataTable(DataTableRequest dt) {
|
||||||
|
// Crear pageable con orden, si existe
|
||||||
|
Pageable pageable = dt.hasOrder()
|
||||||
|
? PageRequest.of(dt.getPage(), dt.getSize(),
|
||||||
|
Sort.by(Sort.Direction.fromString(dt.getOrderDirection()), dt.getOrderColumn()))
|
||||||
|
: PageRequest.of(dt.getPage(), dt.getSize());
|
||||||
|
|
||||||
|
Specification<Printer> spec = PrinterSpecification.buildSpec(dt);
|
||||||
|
Page<Printer> page = printerRepository.findAll(spec, pageable);
|
||||||
|
return new DataTableBuilder<>(page, dt.getDraw()).build();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
package com.printhub.printhub.specification.configuration;
|
||||||
|
|
||||||
|
import com.printhub.printhub.model.configuration.Printer;
|
||||||
|
import com.printhub.printhub.utils.datatables.DataTableRequest;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
|
||||||
|
import jakarta.persistence.criteria.Path;
|
||||||
|
|
||||||
|
import jakarta.persistence.criteria.Predicate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PrinterSpecification {
|
||||||
|
|
||||||
|
public static Specification<Printer> filterBy(String name) {
|
||||||
|
return (root, query, cb) -> {
|
||||||
|
List<Predicate> predicates = new ArrayList<>();
|
||||||
|
|
||||||
|
if (name != null && !name.isBlank()) {
|
||||||
|
predicates.add(cb.like(cb.lower(root.get("name")), "%" + name.toLowerCase() + "%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb.and(predicates.toArray(new Predicate[0]));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Specification<Printer> buildSpec(DataTableRequest dt) {
|
||||||
|
return (root, query, cb) -> {
|
||||||
|
List<Predicate> ands = new ArrayList<>();
|
||||||
|
|
||||||
|
/* --------------- 1. Búsqueda global ---------------- */
|
||||||
|
if (!dt.getGlobalSearch().isBlank()) {
|
||||||
|
String term = "%" + dt.getGlobalSearch().toLowerCase() + "%";
|
||||||
|
|
||||||
|
// OR sobre todas las columnas marcadas como searchable=true
|
||||||
|
List<Predicate> ors = new ArrayList<>();
|
||||||
|
for (var col : dt.getSearchableColumns()) {
|
||||||
|
Path<?> path = root.get(col.getName());
|
||||||
|
|
||||||
|
if (path.getJavaType().equals(String.class)) {
|
||||||
|
ors.add(cb.like(cb.lower(path.as(String.class)), term));
|
||||||
|
}
|
||||||
|
// si quisieras buscar por id cuando el globalSearch es numérico:
|
||||||
|
else if (path.getJavaType().equals(Long.class)) {
|
||||||
|
try {
|
||||||
|
Long value = Long.valueOf(dt.getGlobalSearch());
|
||||||
|
ors.add(cb.equal(path, value));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
/* el término no es número */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// si hay columnas OR, añádelo al AND general
|
||||||
|
if (!ors.isEmpty()) {
|
||||||
|
ands.add(cb.or(ors.toArray(new Predicate[0])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------- 2. Filtros individuales ------------ */
|
||||||
|
for (var col : dt.getColumns()) {
|
||||||
|
if (!col.getFilter().isBlank()) {
|
||||||
|
ands.add(cb.like(cb.lower(root.get(col.getName())),
|
||||||
|
"%" + col.getFilter().toLowerCase() + "%"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------- 3. Resultado ---------------------- */
|
||||||
|
return ands.isEmpty()
|
||||||
|
? cb.conjunction() // sin filtros
|
||||||
|
: cb.and(ands.toArray(new Predicate[0]));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package com.printhub.printhub.utils.datatables;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
public class DataTableBuilder<T> {
|
||||||
|
|
||||||
|
private final int draw;
|
||||||
|
private final Page<T> page;
|
||||||
|
private final List<Function<T, Map<String, Object>>> mappers = new ArrayList<>();
|
||||||
|
private final Map<String, Function<T, Object>> addedColumns = new HashMap<>();
|
||||||
|
private final Map<String, Function<T, Object>> editedColumns = new HashMap<>();
|
||||||
|
|
||||||
|
public DataTableBuilder(Page<T> page, int draw) {
|
||||||
|
this.page = page;
|
||||||
|
this.draw = draw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTableBuilder<T> addColumn(String columnName, Function<T, Object> generator) {
|
||||||
|
addedColumns.put(columnName, generator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTableBuilder<T> editColumn(String columnName, Function<T, Object> replacer) {
|
||||||
|
editedColumns.put(columnName, replacer);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> build() {
|
||||||
|
List<Map<String, Object>> data = page.getContent().stream()
|
||||||
|
.map(entity -> {
|
||||||
|
Map<String, Object> row = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
// Cargar propiedades por defecto
|
||||||
|
Arrays.stream(entity.getClass().getDeclaredFields()).forEach(field -> {
|
||||||
|
field.setAccessible(true);
|
||||||
|
try {
|
||||||
|
row.put(field.getName(), field.get(entity));
|
||||||
|
} catch (IllegalAccessException ignored) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Editar columnas existentes
|
||||||
|
editedColumns.forEach((key, func) -> row.put(key, func.apply(entity)));
|
||||||
|
|
||||||
|
// Añadir columnas nuevas
|
||||||
|
addedColumns.forEach((key, func) -> row.put(key, func.apply(entity)));
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("draw", draw);
|
||||||
|
response.put("recordsTotal", page.getTotalElements());
|
||||||
|
response.put("recordsFiltered", page.getTotalElements()); // cambia si aplicas filtro real
|
||||||
|
response.put("data", data);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
package com.printhub.printhub.utils.datatables;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsula todos los parámetros que DataTables envía al backend
|
||||||
|
* y los deja listos para usarlos en la capa de servicio / specification.
|
||||||
|
*/
|
||||||
|
public class DataTableRequest {
|
||||||
|
|
||||||
|
/* ---------- sub-clase para la metadata de cada columna ---------- */
|
||||||
|
public static class ColumnInfo {
|
||||||
|
private final String name;
|
||||||
|
private final boolean searchable;
|
||||||
|
private final String filter; // columns[i][search][value]
|
||||||
|
|
||||||
|
public ColumnInfo(String name, boolean searchable, String filter) {
|
||||||
|
this.name = name;
|
||||||
|
this.searchable = searchable;
|
||||||
|
this.filter = filter == null ? "" : filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() { return name; }
|
||||||
|
public boolean isSearchable() { return searchable; }
|
||||||
|
public String getFilter() { return filter; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- campos principales ---------- */
|
||||||
|
private final int draw;
|
||||||
|
private final int start;
|
||||||
|
private final int length;
|
||||||
|
|
||||||
|
private final String globalSearch;
|
||||||
|
private final List<ColumnInfo> columns;
|
||||||
|
|
||||||
|
private final String orderColumn; // nombre de la col a ordenar (o null)
|
||||||
|
private final String orderDirection; // asc / desc
|
||||||
|
|
||||||
|
/* ---------- constructor: parsea los parámetros ---------- */
|
||||||
|
public DataTableRequest(Map<String, String> params) {
|
||||||
|
this.draw = Integer.parseInt(params.getOrDefault("draw", "0"));
|
||||||
|
this.start = Integer.parseInt(params.getOrDefault("start", "0"));
|
||||||
|
this.length = Integer.parseInt(params.getOrDefault("length", "10"));
|
||||||
|
|
||||||
|
/* --- búsqueda global --- */
|
||||||
|
this.globalSearch = params.getOrDefault("search[value]", "").trim();
|
||||||
|
|
||||||
|
/* --- columnas --- */
|
||||||
|
List<ColumnInfo> tmp = new ArrayList<>();
|
||||||
|
int i = 0;
|
||||||
|
while (params.containsKey("columns[" + i + "][data]")) {
|
||||||
|
String name = params.get("columns[" + i + "][data]");
|
||||||
|
boolean searchable = Boolean.parseBoolean(params.getOrDefault(
|
||||||
|
"columns[" + i + "][searchable]", "true"));
|
||||||
|
String filter = params.getOrDefault(
|
||||||
|
"columns[" + i + "][search][value]", "");
|
||||||
|
|
||||||
|
tmp.add(new ColumnInfo(name, searchable, filter.trim()));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
this.columns = Collections.unmodifiableList(tmp);
|
||||||
|
|
||||||
|
/* --- orden --- */
|
||||||
|
String ordIdx = params.get("order[0][column]");
|
||||||
|
if (ordIdx != null) {
|
||||||
|
int idx = Integer.parseInt(ordIdx);
|
||||||
|
this.orderColumn = (idx < columns.size()) ? columns.get(idx).getName() : null;
|
||||||
|
this.orderDirection = params.getOrDefault("order[0][dir]", "asc");
|
||||||
|
} else {
|
||||||
|
this.orderColumn = null;
|
||||||
|
this.orderDirection = "asc";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- getters / utilidades ---------- */
|
||||||
|
public int getDraw() { return draw; }
|
||||||
|
public int getPage() { return start / length; }
|
||||||
|
public int getSize() { return length; }
|
||||||
|
|
||||||
|
public boolean hasOrder() { return orderColumn != null; }
|
||||||
|
public String getOrderColumn(){ return orderColumn; }
|
||||||
|
public String getOrderDirection(){ return orderDirection; }
|
||||||
|
|
||||||
|
public String getGlobalSearch() { return globalSearch; }
|
||||||
|
|
||||||
|
/** Devuelve todas las columnas (incluyendo no-searchable) */
|
||||||
|
public List<ColumnInfo> getColumns() { return columns; }
|
||||||
|
|
||||||
|
/** Devuelve solo las columnas con searchable=true */
|
||||||
|
public List<ColumnInfo> getSearchableColumns() {
|
||||||
|
return columns.stream()
|
||||||
|
.filter(ColumnInfo::isSearchable)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Obtiene el filtro individual para una columna por nombre */
|
||||||
|
public String getColumnFilter(String columnName) {
|
||||||
|
return columns.stream()
|
||||||
|
.filter(c -> c.getName().equals(columnName))
|
||||||
|
.map(ColumnInfo::getFilter)
|
||||||
|
.findFirst()
|
||||||
|
.orElse("");
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/main/resources/i18n/en/general.properties
Normal file
1
src/main/resources/i18n/en/general.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
t-paginas=Pages
|
||||||
1
src/main/resources/i18n/es/general.properties
Normal file
1
src/main/resources/i18n/es/general.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
t-paginas=Páginas
|
||||||
0
src/main/resources/static/assets/css/printhub.css
Normal file
0
src/main/resources/static/assets/css/printhub.css
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
(() => {
|
||||||
|
const lang = document.documentElement.lang || 'en'; // “es”, “en”, etc.
|
||||||
|
let langCode;
|
||||||
|
|
||||||
|
switch (lang) {
|
||||||
|
case 'es': langCode = 'es-ES'; break;
|
||||||
|
case 'fr': langCode = 'fr-FR'; break;
|
||||||
|
case 'de': langCode = 'de-DE'; break;
|
||||||
|
default: langCode = 'en-GB';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$('#listOfPrinters').DataTable({
|
||||||
|
processing: true,
|
||||||
|
serverSide: true,
|
||||||
|
language: {
|
||||||
|
url: `https://cdn.datatables.net/plug-ins/1.13.8/i18n/${langCode}.json`
|
||||||
|
},
|
||||||
|
ajax: {
|
||||||
|
url: '/configuration/printers/datatable',
|
||||||
|
dataSrc: 'data'
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{ data: 'id' },
|
||||||
|
{ data: 'name' } // añade aquí 'actions' si la envías
|
||||||
|
],
|
||||||
|
initComplete: function () {
|
||||||
|
const api = this.api();
|
||||||
|
|
||||||
|
// para cada input de la fila 'filters'
|
||||||
|
api.columns().eq(0).each(function (colIdx) {
|
||||||
|
const input = $('.filters th').eq(colIdx).find('input');
|
||||||
|
if (input.length) {
|
||||||
|
$(input).on('keyup change', function () {
|
||||||
|
// envía el texto al backend → columns[colIdx][search][value]
|
||||||
|
api.column(colIdx).search(this.value).draw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
20
src/main/resources/templates/printhub/partials/head-css.html
Normal file
20
src/main/resources/templates/printhub/partials/head-css.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div th:fragment="head-css" th:remove="tag">
|
||||||
|
<!-- Layout config Js -->
|
||||||
|
<script src="/assets/js/layout.js"></script>
|
||||||
|
<!-- Bootstrap Css -->
|
||||||
|
<link href="/assets/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
|
||||||
|
<!-- Icons Css -->
|
||||||
|
<link href="/assets/css/icons.min.css" rel="stylesheet" type="text/css" />
|
||||||
|
<!-- App Css-->
|
||||||
|
<link href="/assets/css/app.min.css" rel="stylesheet" type="text/css" />
|
||||||
|
<!-- custom Css-->
|
||||||
|
<link href="/assets/css/custom.min.css" rel="stylesheet" type="text/css" />
|
||||||
|
|
||||||
|
<link href="/assets/css/printhub.css" rel="stylesheet" type="text/css" />
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user