diff --git a/src/main/java/com/printhub/printhub/model/Breadcrum.java b/src/main/java/com/printhub/printhub/model/Breadcrum.java new file mode 100644 index 0000000..d91fdf1 --- /dev/null +++ b/src/main/java/com/printhub/printhub/model/Breadcrum.java @@ -0,0 +1,42 @@ +package com.printhub.printhub.model; + +import java.util.ArrayList; +import java.util.List; + +public class Breadcrum { + + public List breadcrumb = new ArrayList<>(); + + public void addItem(String label, String dataKey, String url) { + BreadcrumbItem item = new BreadcrumbItem(label, url, dataKey); + breadcrumb.add(item); + } + + public List 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; + } + } +} diff --git a/src/main/java/com/printhub/printhub/model/configuration/Printer.java b/src/main/java/com/printhub/printhub/model/configuration/Printer.java new file mode 100644 index 0000000..47cc4e6 --- /dev/null +++ b/src/main/java/com/printhub/printhub/model/configuration/Printer.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/com/printhub/printhub/repository/configuration/PrinterRepository.java b/src/main/java/com/printhub/printhub/repository/configuration/PrinterRepository.java new file mode 100644 index 0000000..1b6a174 --- /dev/null +++ b/src/main/java/com/printhub/printhub/repository/configuration/PrinterRepository.java @@ -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, JpaSpecificationExecutor { + + // Consulta simple adicional (opcional) + List findByNameContainingIgnoreCase(String name); +} diff --git a/src/main/java/com/printhub/printhub/service/configuration/PrinterService.java b/src/main/java/com/printhub/printhub/service/configuration/PrinterService.java new file mode 100644 index 0000000..44da2dc --- /dev/null +++ b/src/main/java/com/printhub/printhub/service/configuration/PrinterService.java @@ -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 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 spec = PrinterSpecification.buildSpec(dt); + Page page = printerRepository.findAll(spec, pageable); + return new DataTableBuilder<>(page, dt.getDraw()).build(); + + } +} \ No newline at end of file diff --git a/src/main/java/com/printhub/printhub/specification/configuration/PrinterSpecification.java b/src/main/java/com/printhub/printhub/specification/configuration/PrinterSpecification.java new file mode 100644 index 0000000..7707ce8 --- /dev/null +++ b/src/main/java/com/printhub/printhub/specification/configuration/PrinterSpecification.java @@ -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 filterBy(String name) { + return (root, query, cb) -> { + List 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 buildSpec(DataTableRequest dt) { + return (root, query, cb) -> { + List 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 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])); + }; + } + +} diff --git a/src/main/java/com/printhub/printhub/utils/datatables/DataTableBuilder.java b/src/main/java/com/printhub/printhub/utils/datatables/DataTableBuilder.java new file mode 100644 index 0000000..6015d63 --- /dev/null +++ b/src/main/java/com/printhub/printhub/utils/datatables/DataTableBuilder.java @@ -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 { + + private final int draw; + private final Page page; + private final List>> mappers = new ArrayList<>(); + private final Map> addedColumns = new HashMap<>(); + private final Map> editedColumns = new HashMap<>(); + + public DataTableBuilder(Page page, int draw) { + this.page = page; + this.draw = draw; + } + + public DataTableBuilder addColumn(String columnName, Function generator) { + addedColumns.put(columnName, generator); + return this; + } + + public DataTableBuilder editColumn(String columnName, Function replacer) { + editedColumns.put(columnName, replacer); + return this; + } + + public Map build() { + List> data = page.getContent().stream() + .map(entity -> { + Map 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 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; + } +} diff --git a/src/main/java/com/printhub/printhub/utils/datatables/DataTableRequest.java b/src/main/java/com/printhub/printhub/utils/datatables/DataTableRequest.java new file mode 100644 index 0000000..73ab6c4 --- /dev/null +++ b/src/main/java/com/printhub/printhub/utils/datatables/DataTableRequest.java @@ -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 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 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 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 getColumns() { return columns; } + + /** Devuelve solo las columnas con searchable=true */ + public List 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(""); + } +} diff --git a/src/main/resources/i18n/en/general.properties b/src/main/resources/i18n/en/general.properties new file mode 100644 index 0000000..403dc26 --- /dev/null +++ b/src/main/resources/i18n/en/general.properties @@ -0,0 +1 @@ +t-paginas=Pages \ No newline at end of file diff --git a/src/main/resources/i18n/es/general.properties b/src/main/resources/i18n/es/general.properties new file mode 100644 index 0000000..3c0d0aa --- /dev/null +++ b/src/main/resources/i18n/es/general.properties @@ -0,0 +1 @@ +t-paginas=Páginas \ No newline at end of file diff --git a/src/main/resources/static/assets/css/printhub.css b/src/main/resources/static/assets/css/printhub.css new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/static/assets/js/pages/printhub/configuration/printerList.js b/src/main/resources/static/assets/js/pages/printhub/configuration/printerList.js new file mode 100644 index 0000000..4e536a2 --- /dev/null +++ b/src/main/resources/static/assets/js/pages/printhub/configuration/printerList.js @@ -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(); + }); + } + }); + } + }); +})(); \ No newline at end of file diff --git a/src/main/resources/templates/printhub/partials/head-css.html b/src/main/resources/templates/printhub/partials/head-css.html new file mode 100644 index 0000000..adec517 --- /dev/null +++ b/src/main/resources/templates/printhub/partials/head-css.html @@ -0,0 +1,20 @@ + + + +
+ + + + + + + + + + + + +
+ + + \ No newline at end of file