From 7eb17a8da2cea4d6465a2a27d15535f4c9e1468e Mon Sep 17 00:00:00 2001 From: luklpz Date: Sun, 17 May 2026 20:12:44 +0200 Subject: [PATCH] feat(javafx-client): redesign UI to match Android app and fix functional bugs - Add typed DTOs (SyncLogResponse, ProductMappingResponse, OrderMappingResponse, SyncTriggerResponse, AppSettings) replacing fragile Map API - Add SettingsStore (java.util.prefs) for persistent credentials across sessions - Add DateUtils for relative time, duration, localized status labels - Add LogDetailDialog (modal Stage) with status banner and error details - Rewrite DashboardController: 3 sync cards, last log info, sync-all with progress, 30s auto-refresh - Rewrite ProductsController: SKU search, filter chips, status chips, typed table - Rewrite OrdersController: typed table, status chips, Spanish labels - Rewrite LogsController: filter chips, status icons, click row opens LogDetailDialog - Rewrite SettingsController: password toggle, test connection, save to SettingsStore - Rewrite LoginStage: pre-fill from SettingsStore, Material 3 card layout - Rewrite MainStage: 5 tabs (Panel/Productos/Pedidos/Historial/Ajustes), blue header - Rewrite SyncServiceClient to return typed DTOs via Jackson ObjectMapper - Rewrite SessionManager to load/save settings via SettingsStore - Rewrite styles.css: Material 3 palette matching Android app - Fix logback.xml package name (javafx_client -> javafxclient) - Fix dashboard showing oldest log instead of newest (putIfAbsent) - Fix wrong API field names (processed/failed -> itemsProcessed/itemsFailed) - Remove unused javafx-fxml, javafx-swing deps and broken maven-shade-plugin - Add javafx-client/.gitignore and remove target/ from tracking Co-Authored-By: Claude Sonnet 4.6 --- javafx-client/.gitignore | 7 + javafx-client/pom.xml | 42 +- .../javafxclient/api/SyncServiceClient.java | 149 +++----- .../tfg/javafxclient/model/AppSettings.java | 24 ++ .../model/OrderMappingResponse.java | 20 + .../model/ProductMappingResponse.java | 21 + .../javafxclient/model/SessionManager.java | 44 ++- .../javafxclient/model/SyncLogResponse.java | 21 + .../model/SyncTriggerResponse.java | 21 + .../ui/controllers/DashboardController.java | 359 ++++++++++-------- .../ui/controllers/LogsController.java | 310 +++++++-------- .../ui/controllers/OrdersController.java | 259 ++++++------- .../ui/controllers/ProductsController.java | 338 ++++++++--------- .../ui/controllers/SettingsController.java | 168 ++++++++ .../ui/dialogs/LogDetailDialog.java | 154 ++++++++ .../javafxclient/ui/stages/LoginStage.java | 195 +++++----- .../tfg/javafxclient/ui/stages/MainStage.java | 148 ++++---- .../tfg/javafxclient/util/DateUtils.java | 105 +++++ .../tfg/javafxclient/util/SettingsStore.java | 37 ++ javafx-client/src/main/resources/logback.xml | 4 +- javafx-client/src/main/resources/styles.css | 306 +++++++++++---- .../target/classes/applications.properties | 0 .../JavaFxClientApplication.class | Bin 1442 -> 0 bytes .../javafxclient/api/SyncServiceClient.class | Bin 8681 -> 0 bytes .../javafxclient/model/SessionManager.class | Bin 2814 -> 0 bytes .../javafxclient/model/dto/MappingDto.class | Bin 353 -> 0 bytes .../tfg/javafxclient/model/dto/OrderDto.class | Bin 347 -> 0 bytes .../javafxclient/model/dto/ProductDto.class | Bin 353 -> 0 bytes .../tfg/javafxclient/model/dto/SyncDto.class | Bin 344 -> 0 bytes .../ui/controllers/DashboardController.class | Bin 10730 -> 0 bytes .../ui/controllers/LoginController.class | Bin 378 -> 0 bytes .../ui/controllers/LogsController$1.class | Bin 1506 -> 0 bytes .../ui/controllers/LogsController.class | Bin 13325 -> 0 bytes .../ui/controllers/OrdersController$1.class | Bin 1531 -> 0 bytes .../ui/controllers/OrdersController.class | Bin 13165 -> 0 bytes .../ui/controllers/ProductsController$1.class | Bin 1845 -> 0 bytes .../ui/controllers/ProductsController.class | Bin 15178 -> 0 bytes .../javafxclient/ui/stages/LoginStage.class | Bin 9242 -> 0 bytes .../javafxclient/ui/stages/MainStage.class | Bin 8330 -> 0 bytes .../tfg/javafxclient/util/AlertUtil.class | Bin 1299 -> 0 bytes .../tfg/javafxclient/util/FormatUtil.class | Bin 343 -> 0 bytes .../tfg/javafxclient/util/TaskExecutor.class | Bin 349 -> 0 bytes .../target/classes/fxml/dashboard.fxml | 0 javafx-client/target/classes/fxml/login.fxml | 0 javafx-client/target/classes/fxml/logs.fxml | 0 javafx-client/target/classes/fxml/orders.fxml | 0 .../target/classes/fxml/products.fxml | 0 javafx-client/target/classes/logback.xml | 14 - javafx-client/target/classes/styles.css | 93 ----- .../compile/default-compile/createdFiles.lst | 20 - .../compile/default-compile/inputFiles.lst | 17 - 51 files changed, 1654 insertions(+), 1222 deletions(-) create mode 100644 javafx-client/.gitignore create mode 100644 javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/AppSettings.java create mode 100644 javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/OrderMappingResponse.java create mode 100644 javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/ProductMappingResponse.java create mode 100644 javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SyncLogResponse.java create mode 100644 javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SyncTriggerResponse.java create mode 100644 javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/SettingsController.java create mode 100644 javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/dialogs/LogDetailDialog.java create mode 100644 javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/DateUtils.java create mode 100644 javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/SettingsStore.java delete mode 100644 javafx-client/target/classes/applications.properties delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/JavaFxClientApplication.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/api/SyncServiceClient.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/SessionManager.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/dto/MappingDto.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/dto/OrderDto.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/dto/ProductDto.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/dto/SyncDto.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/DashboardController.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LoginController.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController$1.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/OrdersController$1.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/OrdersController.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController$1.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/stages/LoginStage.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/stages/MainStage.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/util/AlertUtil.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/util/FormatUtil.class delete mode 100644 javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/util/TaskExecutor.class delete mode 100644 javafx-client/target/classes/fxml/dashboard.fxml delete mode 100644 javafx-client/target/classes/fxml/login.fxml delete mode 100644 javafx-client/target/classes/fxml/logs.fxml delete mode 100644 javafx-client/target/classes/fxml/orders.fxml delete mode 100644 javafx-client/target/classes/fxml/products.fxml delete mode 100644 javafx-client/target/classes/logback.xml delete mode 100644 javafx-client/target/classes/styles.css delete mode 100644 javafx-client/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst delete mode 100644 javafx-client/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst diff --git a/javafx-client/.gitignore b/javafx-client/.gitignore new file mode 100644 index 0000000..80b9b40 --- /dev/null +++ b/javafx-client/.gitignore @@ -0,0 +1,7 @@ +target/ +*.class +*.jar +*.log +.idea/ +*.iml +.DS_Store diff --git a/javafx-client/pom.xml b/javafx-client/pom.xml index 6b7b70a..8065e4b 100644 --- a/javafx-client/pom.xml +++ b/javafx-client/pom.xml @@ -20,17 +20,12 @@ - + org.openjfx javafx-controls ${javafx.version} - - org.openjfx - javafx-fxml - ${javafx.version} - org.openjfx javafx-graphics @@ -41,13 +36,8 @@ javafx-base ${javafx.version} - - org.openjfx - javafx-swing - ${javafx.version} - - + com.fasterxml.jackson.core jackson-databind @@ -107,7 +97,7 @@ - + org.openjfx javafx-maven-plugin @@ -116,30 +106,6 @@ com.teterialosjuanjos.tfg.javafxclient.JavaFxClientApplication - - - - org.apache.maven.plugins - maven-shade-plugin - 3.5.0 - - - package - - shade - - - true - fat - - - com.teterialosjuanjos.tfg.javafxclient.JavaFxClientApplication - - - - - - - \ No newline at end of file + diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/api/SyncServiceClient.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/api/SyncServiceClient.java index b5c78ae..015f937 100644 --- a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/api/SyncServiceClient.java +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/api/SyncServiceClient.java @@ -1,7 +1,8 @@ package com.teterialosjuanjos.tfg.javafxclient.api; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.teterialosjuanjos.tfg.javafxclient.model.*; import lombok.extern.slf4j.Slf4j; import java.io.IOException; @@ -10,12 +11,12 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Base64; +import java.util.List; /** - * Cliente HTTP tipado para el sync-service en Railway. - * - *

Maneja autenticación Basic Auth para todos los endpoints.

+ * Cliente HTTP tipado para el sync-service. + * Usa java.net.http.HttpClient (Java 11+) con Basic Auth. */ @Slf4j public class SyncServiceClient { @@ -26,133 +27,81 @@ public class SyncServiceClient { private final ObjectMapper objectMapper; public SyncServiceClient(String baseUrl, String username, String password) { - this.baseUrl = baseUrl.replaceAll("/$", ""); // Remove trailing slash + this.baseUrl = baseUrl.replaceAll("/$", ""); this.httpClient = HttpClient.newHttpClient(); this.authHeader = buildBasicAuthHeader(username, password); - this.objectMapper = new ObjectMapper(); - this.objectMapper.registerModule(new JavaTimeModule()); + this.objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } - // ── Health Check ──────────────────────────────────────────────────────── + // ── Health ────────────────────────────────────────────────────────────── - public Map checkHealth() throws IOException, InterruptedException { - String url = baseUrl + "/actuator/health"; - return getJson(url, Map.class); + /** Lanza IOException si el servidor no responde 200. */ + public void checkHealth() throws IOException, InterruptedException { + HttpResponse response = sendRequest("GET", baseUrl + "/actuator/health", null); + if (response.statusCode() != 200) { + throw new IOException("Health check failed: HTTP " + response.statusCode()); + } } // ── Logs ───────────────────────────────────────────────────────────────── - /** - * Obtiene todos los logs de sincronización. - * - * @param type opcional, filtra por tipo (PRODUCT_PUSH, STOCK_PUSH, ORDER_PULL) - */ - public List> getLogs(String type) throws IOException, InterruptedException { - String url = baseUrl + "/api/logs"; - if (type != null && !type.isEmpty()) { - url += "?type=" + type; - } - - HttpResponse response = sendRequest("GET", url, null); - if (response.statusCode() == 200) { - return objectMapper.readValue( - response.body(), - objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class) - ); - } - throw new IOException("Failed to fetch logs: " + response.statusCode()); + public List getLogs(String type) throws IOException, InterruptedException { + String url = baseUrl + "/api/logs" + (type != null && !type.isEmpty() ? "?type=" + type : ""); + return getList(url, SyncLogResponse.class); } - /** - * Obtiene un log específico por ID. - */ - public Map getLog(Long id) throws IOException, InterruptedException { - String url = baseUrl + "/api/logs/" + id; - return getJson(url, Map.class); + public SyncLogResponse getLog(Long id) throws IOException, InterruptedException { + return get(baseUrl + "/api/logs/" + id, SyncLogResponse.class); } // ── Mappings ───────────────────────────────────────────────────────────── - /** - * Obtiene todos los mapeos de productos. - * - * @param status opcional, filtra por status (PENDING, SYNCED, ERROR) - */ - public List> getProductMappings(String status) - throws IOException, InterruptedException { - String url = baseUrl + "/api/mappings/products"; - if (status != null && !status.isEmpty()) { - url += "?status=" + status; - } - - HttpResponse response = sendRequest("GET", url, null); - if (response.statusCode() == 200) { - return objectMapper.readValue( - response.body(), - objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class) - ); - } - throw new IOException("Failed to fetch product mappings: " + response.statusCode()); + public List getProductMappings(String status) throws IOException, InterruptedException { + String url = baseUrl + "/api/mappings/products" + + (status != null && !status.isEmpty() ? "?status=" + status : ""); + return getList(url, ProductMappingResponse.class); } - /** - * Obtiene todos los mapeos de pedidos. - */ - public List> getOrderMappings() throws IOException, InterruptedException { - String url = baseUrl + "/api/mappings/orders"; - - HttpResponse response = sendRequest("GET", url, null); - if (response.statusCode() == 200) { - return objectMapper.readValue( - response.body(), - objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class) - ); - } - throw new IOException("Failed to fetch order mappings: " + response.statusCode()); + public List getOrderMappings() throws IOException, InterruptedException { + return getList(baseUrl + "/api/mappings/orders", OrderMappingResponse.class); } - // ── Sync Triggers ─────────────────────────────────────────────────────── + // ── Sync Triggers ──────────────────────────────────────────────────────── - /** - * Dispara sincronización de productos. - */ - public Map triggerProductSync() throws IOException, InterruptedException { - String url = baseUrl + "/api/sync/products"; - return postJson(url, new HashMap<>(), Map.class); + public SyncTriggerResponse triggerProductSync() throws IOException, InterruptedException { + return post(baseUrl + "/api/sync/products", SyncTriggerResponse.class); } - /** - * Dispara sincronización de stock. - */ - public Map triggerStockSync() throws IOException, InterruptedException { - String url = baseUrl + "/api/sync/stock"; - return postJson(url, new HashMap<>(), Map.class); + public SyncTriggerResponse triggerStockSync() throws IOException, InterruptedException { + return post(baseUrl + "/api/sync/stock", SyncTriggerResponse.class); } - /** - * Dispara sincronización de pedidos. - */ - public Map triggerOrderSync() throws IOException, InterruptedException { - String url = baseUrl + "/api/sync/orders"; - return postJson(url, new HashMap<>(), Map.class); + public SyncTriggerResponse triggerOrderSync() throws IOException, InterruptedException { + return post(baseUrl + "/api/sync/orders", SyncTriggerResponse.class); } // ── Helpers ────────────────────────────────────────────────────────────── - private T getJson(String url, Class responseType) throws IOException, InterruptedException { + private T get(String url, Class type) throws IOException, InterruptedException { + HttpResponse response = sendRequest("GET", url, null); + if (response.statusCode() == 200) return objectMapper.readValue(response.body(), type); + throw new IOException("HTTP " + response.statusCode() + ": " + response.body()); + } + + private List getList(String url, Class elementType) throws IOException, InterruptedException { HttpResponse response = sendRequest("GET", url, null); if (response.statusCode() == 200) { - return objectMapper.readValue(response.body(), responseType); + return objectMapper.readValue(response.body(), + objectMapper.getTypeFactory().constructCollectionType(List.class, elementType)); } throw new IOException("HTTP " + response.statusCode() + ": " + response.body()); } - private T postJson(String url, Object body, Class responseType) - throws IOException, InterruptedException { - String jsonBody = objectMapper.writeValueAsString(body); - HttpResponse response = sendRequest("POST", url, jsonBody); + private T post(String url, Class type) throws IOException, InterruptedException { + HttpResponse response = sendRequest("POST", url, "{}"); if (response.statusCode() == 200 || response.statusCode() == 201) { - return objectMapper.readValue(response.body(), responseType); + return objectMapper.readValue(response.body(), type); } throw new IOException("HTTP " + response.statusCode() + ": " + response.body()); } @@ -166,9 +115,7 @@ public class SyncServiceClient { .method(method, body != null ? HttpRequest.BodyPublishers.ofString(body) : HttpRequest.BodyPublishers.noBody()); - - HttpRequest request = builder.build(); - return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + return httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString()); } private String buildBasicAuthHeader(String username, String password) { @@ -176,4 +123,4 @@ public class SyncServiceClient { String encoded = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)); return "Basic " + encoded; } -} \ No newline at end of file +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/AppSettings.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/AppSettings.java new file mode 100644 index 0000000..6a36304 --- /dev/null +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/AppSettings.java @@ -0,0 +1,24 @@ +package com.teterialosjuanjos.tfg.javafxclient.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +/** Configuración de conexión al sync-service. */ +@Getter +@Setter +@AllArgsConstructor +public class AppSettings { + + private String baseUrl; + private String username; + private String password; + + public static AppSettings defaults() { + return new AppSettings( + "https://proyectointermodular-production-a9c3.up.railway.app", + "admin", + "admin123" + ); + } +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/OrderMappingResponse.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/OrderMappingResponse.java new file mode 100644 index 0000000..fa01206 --- /dev/null +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/OrderMappingResponse.java @@ -0,0 +1,20 @@ +package com.teterialosjuanjos.tfg.javafxclient.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** Respuesta de la API para un mapeo de pedido. */ +@Getter +@Setter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class OrderMappingResponse { + private Long id; + private Integer prestashopOrderId; + private Integer dolibarrOrderId; + private Integer dolibarrInvoiceId; + private String importedAt; // ISO-8601 + private String status; // IMPORTED | INVOICED | ERROR +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/ProductMappingResponse.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/ProductMappingResponse.java new file mode 100644 index 0000000..bc2d2db --- /dev/null +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/ProductMappingResponse.java @@ -0,0 +1,21 @@ +package com.teterialosjuanjos.tfg.javafxclient.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** Respuesta de la API para un mapeo de producto. */ +@Getter +@Setter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class ProductMappingResponse { + private Long id; + private String sku; + private Integer dolibarrId; + private Integer prestashopId; + private String lastSyncedAt; // ISO-8601, nullable + private String syncStatus; // SYNCED | PENDING | ERROR + private String errorMessage; +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SessionManager.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SessionManager.java index 9625931..2f0bf9b 100644 --- a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SessionManager.java +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SessionManager.java @@ -1,12 +1,14 @@ package com.teterialosjuanjos.tfg.javafxclient.model; import com.teterialosjuanjos.tfg.javafxclient.api.SyncServiceClient; +import com.teterialosjuanjos.tfg.javafxclient.util.SettingsStore; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; /** - * Gestiona la sesión actual: credenciales, cliente HTTP, estado de autenticación. + * Gestiona la sesión actual: credenciales, cliente HTTP y persistencia de ajustes. + * Carga los ajustes guardados en el constructor. */ @Slf4j @Getter @@ -20,16 +22,16 @@ public class SessionManager { private boolean authenticated; public SessionManager() { + AppSettings saved = SettingsStore.load(); + this.baseUrl = saved.getBaseUrl(); + this.username = saved.getUsername(); + this.password = saved.getPassword(); this.authenticated = false; - // Default: Railway production - this.baseUrl = "https://proyectointermodular-production-a9c3.up.railway.app"; } /** - * Intenta autenticarse contra el sync-service. - * Si es exitoso, inicializa el cliente HTTP. - * - * @return true si auth fue exitosa + * Autentica contra el sync-service verificando el health endpoint. + * Si tiene éxito, guarda los ajustes en SettingsStore. */ public boolean authenticate(String baseUrl, String username, String password) { this.baseUrl = baseUrl; @@ -38,9 +40,9 @@ public class SessionManager { try { this.syncServiceClient = new SyncServiceClient(baseUrl, username, password); - // Prueba conectividad syncServiceClient.checkHealth(); this.authenticated = true; + SettingsStore.save(new AppSettings(baseUrl, username, password)); log.info("Authentication successful for user: {}", username); return true; } catch (Exception e) { @@ -50,6 +52,30 @@ public class SessionManager { } } + /** + * Actualiza los ajustes y reinicializa el cliente HTTP sin health check. + * Usado desde la pantalla de ajustes para cambiar la configuración en caliente. + */ + public void updateSettings(String baseUrl, String username, String password) { + this.baseUrl = baseUrl; + this.username = username; + this.password = password; + this.syncServiceClient = new SyncServiceClient(baseUrl, username, password); + SettingsStore.save(new AppSettings(baseUrl, username, password)); + log.info("Settings updated, new base URL: {}", baseUrl); + } + + /** Comprueba conectividad con los ajustes dados sin modificar el estado. */ + public boolean testConnection(String baseUrl, String username, String password) { + try { + new SyncServiceClient(baseUrl, username, password).checkHealth(); + return true; + } catch (Exception e) { + log.debug("Connection test failed: {}", e.getMessage()); + return false; + } + } + public void logout() { this.authenticated = false; this.syncServiceClient = null; @@ -57,4 +83,4 @@ public class SessionManager { this.password = null; log.info("Logged out"); } -} \ No newline at end of file +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SyncLogResponse.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SyncLogResponse.java new file mode 100644 index 0000000..8a4c1a5 --- /dev/null +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SyncLogResponse.java @@ -0,0 +1,21 @@ +package com.teterialosjuanjos.tfg.javafxclient.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** Respuesta de la API para un registro de sincronización. */ +@Getter +@Setter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class SyncLogResponse { + private Long id; + private String syncType; + private String startedAt; // ISO-8601 + private String finishedAt; // ISO-8601, null si en curso + private Integer itemsProcessed; + private Integer itemsFailed; + private String errorDetails; +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SyncTriggerResponse.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SyncTriggerResponse.java new file mode 100644 index 0000000..e3136cf --- /dev/null +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SyncTriggerResponse.java @@ -0,0 +1,21 @@ +package com.teterialosjuanjos.tfg.javafxclient.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +/** Respuesta de la API al disparar una sincronización manual. */ +@Getter +@Setter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class SyncTriggerResponse { + private String syncType; + private int itemsProcessed; + private int itemsFailed; + private boolean hasErrors; + private List errors; +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/DashboardController.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/DashboardController.java index ce7517a..adccb96 100644 --- a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/DashboardController.java +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/DashboardController.java @@ -2,40 +2,37 @@ package com.teterialosjuanjos.tfg.javafxclient.ui.controllers; import com.teterialosjuanjos.tfg.javafxclient.api.SyncServiceClient; import com.teterialosjuanjos.tfg.javafxclient.model.SessionManager; +import com.teterialosjuanjos.tfg.javafxclient.model.SyncLogResponse; +import com.teterialosjuanjos.tfg.javafxclient.model.SyncTriggerResponse; import com.teterialosjuanjos.tfg.javafxclient.util.AlertUtil; +import com.teterialosjuanjos.tfg.javafxclient.util.DateUtils; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; -import javafx.scene.control.Label; -import javafx.scene.paint.Color; +import javafx.scene.control.*; +import javafx.scene.layout.*; import javafx.util.Duration; import lombok.extern.slf4j.Slf4j; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; +import java.util.HashMap; import java.util.List; import java.util.Map; /** - * Dashboard con estado de sincronizaciones y estadísticas. + * Tab Dashboard: botón "Sincronizar todo", 3 tarjetas de sync con datos del último log. + * Equivalente a DashboardScreen de la app Android. */ @Slf4j public class DashboardController { private final SessionManager sessionManager; - private Label lastProductSyncLabel; - private Label lastStockSyncLabel; - private Label lastOrderSyncLabel; - private Label successRateLabel; - private Label totalSyncsLabel; + + private final Map lastSyncInfoLabels = new HashMap<>(); + private Button syncAllButton; + private Label syncAllStatusLabel; public DashboardController(SessionManager sessionManager) { this.sessionManager = sessionManager; @@ -43,190 +40,222 @@ public class DashboardController { public VBox createView() { VBox root = new VBox(20); - root.setPadding(new Insets(20)); - root.setStyle("-fx-background-color: #fafafa;"); + root.setPadding(new Insets(24)); + root.setStyle("-fx-background-color: #FAFAFA;"); - // Title - Label title = new Label("Synchronization Dashboard"); - title.setStyle("-fx-font-size: 18; -fx-font-weight: bold;"); + // Header + Label appTitle = new Label("Sync Manager"); + appTitle.setStyle("-fx-font-size: 22; -fx-font-weight: bold; -fx-text-fill: #1565C0;"); + Label appSubtitle = new Label("Dolibarr · PrestaShop"); + appSubtitle.setStyle("-fx-font-size: 12; -fx-text-fill: #757575;"); + VBox headerBox = new VBox(2, appTitle, appSubtitle); - // Sync stats cards - GridPane statsGrid = createStatsGrid(); - - // Action buttons - HBox actionsBox = createActionsBox(); - - root.getChildren().addAll( - title, - statsGrid, - actionsBox, - new Label() // spacer + // Sync All button + syncAllButton = new Button("Sincronizar todo"); + syncAllButton.setMaxWidth(Double.MAX_VALUE); + syncAllButton.setPrefHeight(46); + syncAllButton.setStyle( + "-fx-background-color: #1565C0; -fx-text-fill: white; -fx-font-size: 14; " + + "-fx-font-weight: bold; -fx-background-radius: 10; -fx-cursor: hand;" ); + syncAllButton.setOnAction(e -> handleSyncAll()); - // Auto-refresh cada 10 segundos - startAutoRefresh(); + syncAllStatusLabel = new Label(""); + syncAllStatusLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #1565C0;"); + syncAllStatusLabel.setVisible(false); + + // 3 sync cards + HBox cardsBox = new HBox(16); + VBox productCard = createSyncCard("📦", "Productos", "Dolibarr → PrestaShop", "PRODUCT_PUSH", "#1565C0"); + VBox stockCard = createSyncCard("📊", "Stock", "Dolibarr → PrestaShop", "STOCK_PUSH", "#2E7D32"); + VBox ordersCard = createSyncCard("🛒", "Pedidos", "PrestaShop → Dolibarr", "ORDER_PULL", "#E65100"); + + HBox.setHgrow(productCard, Priority.ALWAYS); + HBox.setHgrow(stockCard, Priority.ALWAYS); + HBox.setHgrow(ordersCard, Priority.ALWAYS); + cardsBox.getChildren().addAll(productCard, stockCard, ordersCard); + + root.getChildren().addAll(headerBox, syncAllButton, syncAllStatusLabel, cardsBox); - // Carga inicial refreshDashboard(); + startAutoRefresh(); return root; } - private GridPane createStatsGrid() { - GridPane grid = new GridPane(); - grid.setHgap(15); - grid.setVgap(15); - grid.setPadding(new Insets(10)); - grid.setStyle("-fx-background-color: white; -fx-border-color: #e0e0e0; -fx-border-radius: 4;"); - - // Card: Last Product Sync - VBox productCard = createStatCard( - "📦 Last Product Sync", - "Never", - "#1976d2" - ); - lastProductSyncLabel = (Label) productCard.getChildren().get(1); - GridPane.setConstraints(productCard, 0, 0); - - // Card: Last Stock Sync - VBox stockCard = createStatCard( - "📊 Last Stock Sync", - "Never", - "#388e3c" - ); - lastStockSyncLabel = (Label) stockCard.getChildren().get(1); - GridPane.setConstraints(stockCard, 1, 0); - - // Card: Last Order Sync - VBox orderCard = createStatCard( - "🛒 Last Order Sync", - "Never", - "#f57c00" - ); - lastOrderSyncLabel = (Label) orderCard.getChildren().get(1); - GridPane.setConstraints(orderCard, 2, 0); - - // Card: Total Syncs - VBox totalCard = createStatCard( - "📈 Total Syncs", - "0", - "#6a1b9a" - ); - totalSyncsLabel = (Label) totalCard.getChildren().get(1); - GridPane.setConstraints(totalCard, 0, 1); - - // Card: Success Rate - VBox rateCard = createStatCard( - "✓ Success Rate", - "0%", - "#00796b" - ); - successRateLabel = (Label) rateCard.getChildren().get(1); - GridPane.setConstraints(rateCard, 1, 1); - - grid.getChildren().addAll(productCard, stockCard, orderCard, totalCard, rateCard); - - return grid; - } - - private VBox createStatCard(String title, String value, String color) { + private VBox createSyncCard(String icon, String title, String direction, + String type, String color) { VBox card = new VBox(10); - card.setPadding(new Insets(15)); + card.setPadding(new Insets(16)); card.setStyle( - "-fx-background-color: white; " + - "-fx-border-color: " + color + "; " + - "-fx-border-width: 3 0 0 0; " + - "-fx-border-radius: 0;" + "-fx-background-color: white;" + + "-fx-background-radius: 12;" + + "-fx-border-radius: 12;" + + "-fx-border-color: #E0E0E0 #E0E0E0 #E0E0E0 " + color + ";" + + "-fx-border-width: 1 1 1 4;" + + "-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.06), 6, 0, 0, 2);" ); - card.setMinWidth(250); - card.setAlignment(Pos.TOP_LEFT); - Label titleLabel = new Label(title); - titleLabel.setStyle("-fx-font-size: 13; -fx-font-weight: bold; -fx-text-fill: " + color + ";"); + // Title row + button + HBox headerRow = new HBox(10); + headerRow.setAlignment(Pos.CENTER_LEFT); - Label valueLabel = new Label(value); - valueLabel.setStyle("-fx-font-size: 16; -fx-font-weight: bold; -fx-text-fill: #333333;"); + Label titleLabel = new Label(icon + " " + title); + titleLabel.setStyle("-fx-font-size: 14; -fx-font-weight: bold; -fx-text-fill: " + color + ";"); - card.getChildren().addAll(titleLabel, valueLabel); + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + Button syncBtn = new Button("Sincronizar"); + syncBtn.setStyle( + "-fx-background-color: " + color + "; -fx-text-fill: white; " + + "-fx-background-radius: 8; -fx-padding: 6 14; -fx-font-size: 11; -fx-cursor: hand;" + ); + syncBtn.setOnAction(e -> handleSingleSync(type, syncBtn)); + + headerRow.getChildren().addAll(titleLabel, spacer, syncBtn); + + Label dirLabel = new Label(direction); + dirLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #9E9E9E;"); + + Label lastSyncLabel = new Label("Sin sincronizaciones previas"); + lastSyncLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #BDBDBD;"); + lastSyncInfoLabels.put(type, lastSyncLabel); + + card.getChildren().addAll(headerRow, dirLabel, lastSyncLabel); return card; } - private HBox createActionsBox() { - HBox box = new HBox(10); - box.setPadding(new Insets(15)); - box.setStyle("-fx-background-color: white; -fx-border-color: #e0e0e0;"); - box.setAlignment(Pos.CENTER_LEFT); - - Label label = new Label("Quick Actions:"); - label.setStyle("-fx-font-weight: bold;"); - - // Buttons - javafx.scene.control.Button refreshButton = new javafx.scene.control.Button("🔄 Refresh Now"); - refreshButton.setStyle("-fx-font-size: 11; -fx-padding: 8;"); - refreshButton.setOnAction(e -> refreshDashboard()); - - box.getChildren().addAll(label, refreshButton); - - return box; - } - public void refreshDashboard() { - Thread thread = new Thread(() -> { + Thread t = new Thread(() -> { try { SyncServiceClient client = sessionManager.getSyncServiceClient(); - List> logs = client.getLogs(null); - - Platform.runLater(() -> updateDashboard(logs)); + List logs = client.getLogs(null); + Platform.runLater(() -> updateCards(logs)); } catch (Exception e) { - log.error("Error refreshing dashboard: {}", e.getMessage()); + log.error("Dashboard refresh failed: {}", e.getMessage()); } }); - thread.setDaemon(true); - thread.start(); + t.setDaemon(true); + t.start(); } - private void updateDashboard(List> logs) { - if (logs.isEmpty()) { - totalSyncsLabel.setText("0"); - successRateLabel.setText("0%"); - return; - } - - totalSyncsLabel.setText(String.valueOf(logs.size())); - - // Busca último sync de cada tipo - for (Map log : logs) { - String syncType = (String) log.get("syncType"); - Instant startedAt = Instant.parse((String) log.get("startedAt")); - String timeStr = formatInstant(startedAt); - - switch (syncType) { - case "PRODUCT_PUSH" -> lastProductSyncLabel.setText(timeStr); - case "STOCK_PUSH" -> lastStockSyncLabel.setText(timeStr); - case "ORDER_PULL" -> lastOrderSyncLabel.setText(timeStr); + private void updateCards(List logs) { + // Toma el más reciente de cada tipo (el primero encontrado en la lista, que viene newest-first) + Map latestPerType = new HashMap<>(); + for (SyncLogResponse entry : logs) { + if (entry.getSyncType() != null) { + latestPerType.putIfAbsent(entry.getSyncType(), entry); } } - // Success rate - long successCount = logs.stream() - .filter(l -> ((Number) l.get("itemsFailed")).longValue() == 0) - .count(); - long successRate = (successCount * 100) / logs.size(); - successRateLabel.setText(successRate + "%"); + latestPerType.forEach((type, entry) -> { + Label label = lastSyncInfoLabels.get(type); + if (label == null) return; + + String time = DateUtils.formatRelative(entry.getStartedAt()); + int processed = entry.getItemsProcessed() != null ? entry.getItemsProcessed() : 0; + int failed = entry.getItemsFailed() != null ? entry.getItemsFailed() : 0; + String text = time + " · " + processed + " procesados"; + if (failed > 0) text += ", " + failed + " fallidos"; + + label.setText(text); + label.setStyle("-fx-font-size: 11; -fx-text-fill: " + (failed > 0 ? "#C62828" : "#757575") + ";"); + }); + } + + private void handleSingleSync(String type, Button btn) { + Alert confirm = new Alert(Alert.AlertType.CONFIRMATION); + confirm.setTitle("Confirmar sincronización"); + confirm.setHeaderText("¿Ejecutar sincronización de " + DateUtils.syncTypeLabel(type) + "?"); + confirm.setContentText(DateUtils.syncTypeDirection(type)); + if (confirm.showAndWait().orElse(ButtonType.CANCEL) != ButtonType.OK) return; + + btn.setDisable(true); + Thread t = new Thread(() -> { + try { + SyncServiceClient client = sessionManager.getSyncServiceClient(); + SyncTriggerResponse result = switch (type) { + case "PRODUCT_PUSH" -> client.triggerProductSync(); + case "STOCK_PUSH" -> client.triggerStockSync(); + case "ORDER_PULL" -> client.triggerOrderSync(); + default -> throw new IllegalArgumentException("Unknown type: " + type); + }; + Platform.runLater(() -> { + btn.setDisable(false); + if (result.getItemsFailed() == 0) { + AlertUtil.showInfo("Completado", + DateUtils.syncTypeLabel(type) + ": " + result.getItemsProcessed() + " procesados."); + } else { + AlertUtil.showWarn("Completado con errores", + result.getItemsProcessed() + " procesados, " + result.getItemsFailed() + " fallidos."); + } + refreshDashboard(); + }); + } catch (Exception e) { + Platform.runLater(() -> { + btn.setDisable(false); + AlertUtil.showError("Error de sincronización", e.getMessage()); + }); + } + }); + t.setDaemon(true); + t.start(); + } + + private void handleSyncAll() { + Alert confirm = new Alert(Alert.AlertType.CONFIRMATION); + confirm.setTitle("Sincronizar todo"); + confirm.setHeaderText("Ejecutará productos, stock y pedidos en secuencia."); + confirm.setContentText("¿Continuar?"); + if (confirm.showAndWait().orElse(ButtonType.CANCEL) != ButtonType.OK) return; + + syncAllButton.setDisable(true); + syncAllStatusLabel.setVisible(true); + + Thread t = new Thread(() -> { + try { + SyncServiceClient client = sessionManager.getSyncServiceClient(); + + Platform.runLater(() -> syncAllStatusLabel.setText("Sincronizando productos (1/3)...")); + SyncTriggerResponse r1 = client.triggerProductSync(); + + Platform.runLater(() -> syncAllStatusLabel.setText("Sincronizando stock (2/3)...")); + SyncTriggerResponse r2 = client.triggerStockSync(); + + Platform.runLater(() -> syncAllStatusLabel.setText("Sincronizando pedidos (3/3)...")); + SyncTriggerResponse r3 = client.triggerOrderSync(); + + int totalProcessed = r1.getItemsProcessed() + r2.getItemsProcessed() + r3.getItemsProcessed(); + int totalFailed = r1.getItemsFailed() + r2.getItemsFailed() + r3.getItemsFailed(); + + Platform.runLater(() -> { + syncAllButton.setDisable(false); + syncAllStatusLabel.setVisible(false); + if (totalFailed == 0) { + AlertUtil.showInfo("Sincronización completada", + "Total procesados: " + totalProcessed); + } else { + AlertUtil.showWarn("Completado con errores", + "Procesados: " + totalProcessed + ", Fallidos: " + totalFailed); + } + refreshDashboard(); + }); + } catch (Exception e) { + Platform.runLater(() -> { + syncAllButton.setDisable(false); + syncAllStatusLabel.setVisible(false); + AlertUtil.showError("Error en sincronización", e.getMessage()); + }); + } + }); + t.setDaemon(true); + t.start(); } private void startAutoRefresh() { - Timeline timeline = new Timeline( - new KeyFrame(Duration.seconds(10), e -> refreshDashboard()) - ); + Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(30), e -> refreshDashboard())); timeline.setCycleCount(Animation.INDEFINITE); timeline.play(); } - - private String formatInstant(Instant instant) { - LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); - return ldt.format(formatter); - } -} \ No newline at end of file +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController.java index 89ac883..5607b83 100644 --- a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController.java +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController.java @@ -2,221 +2,187 @@ package com.teterialosjuanjos.tfg.javafxclient.ui.controllers; import com.teterialosjuanjos.tfg.javafxclient.api.SyncServiceClient; import com.teterialosjuanjos.tfg.javafxclient.model.SessionManager; +import com.teterialosjuanjos.tfg.javafxclient.model.SyncLogResponse; +import com.teterialosjuanjos.tfg.javafxclient.ui.dialogs.LogDetailDialog; import com.teterialosjuanjos.tfg.javafxclient.util.AlertUtil; +import com.teterialosjuanjos.tfg.javafxclient.util.DateUtils; import javafx.application.Platform; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.*; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import javafx.scene.layout.VBox; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import javafx.scene.layout.*; import lombok.extern.slf4j.Slf4j; import java.util.List; -import java.util.Map; /** - * Tab para logs de sincronización. + * Tab de historial de sincronizaciones. + * Click en una fila abre LogDetailDialog. + * Equivalente a LogsScreen de la app Android. */ @Slf4j public class LogsController { private final SessionManager sessionManager; - private TableView> logsTable; - private TextArea errorDetailsArea; + private TableView logsTable; public LogsController(SessionManager sessionManager) { this.sessionManager = sessionManager; } public VBox createView() { - VBox root = new VBox(15); + VBox root = new VBox(12); root.setPadding(new Insets(20)); - root.setStyle("-fx-background-color: #fafafa;"); + root.setStyle("-fx-background-color: #FAFAFA;"); - // Title - Label title = new Label("Synchronization Logs"); - title.setStyle("-fx-font-size: 18; -fx-font-weight: bold;"); + // Header + HBox headerRow = new HBox(10); + headerRow.setAlignment(Pos.CENTER_LEFT); + VBox titleBox = new VBox(2); + Label title = new Label("Historial de sincronización"); + title.setStyle("-fx-font-size: 20; -fx-font-weight: bold;"); + Label subtitle = new Label("Registros de cada ejecución"); + subtitle.setStyle("-fx-font-size: 12; -fx-text-fill: #757575;"); + titleBox.getChildren().addAll(title, subtitle); + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + Button refreshBtn = new Button("🔄 Actualizar"); + refreshBtn.setStyle("-fx-background-color: #E0E0E0; -fx-background-radius: 8; -fx-cursor: hand; -fx-padding: 8 14;"); + refreshBtn.setOnAction(e -> loadLogs(null)); + headerRow.getChildren().addAll(titleBox, spacer, refreshBtn); + + // Filter chips + HBox chipsBox = createFilterChips(); // Table - logsTable = createLogsTable(); - VBox.setVgrow(logsTable, javafx.scene.layout.Priority.ALWAYS); - logsTable.getSelectionModel().selectedItemProperty().addListener((obs, old, newVal) -> { - if (newVal != null) { - showLogDetails(newVal); + logsTable = createTable(); + VBox.setVgrow(logsTable, Priority.ALWAYS); + + // Click row → open detail dialog + logsTable.getSelectionModel().selectedItemProperty().addListener((obs, old, log) -> { + if (log != null) { + new LogDetailDialog(log).show(); + Platform.runLater(() -> logsTable.getSelectionModel().clearSelection()); } }); - // Details panel - VBox detailsPanel = createDetailsPanel(); - - // Filter & Actions - HBox filterBox = createFilterBox(); - - root.getChildren().addAll(title, filterBox, logsTable, detailsPanel); - - // Load initial data + root.getChildren().addAll(headerRow, chipsBox, logsTable); loadLogs(null); - return root; } - private TableView> createLogsTable() { - TableView> table = new TableView<>(); - table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS); - table.setStyle("-fx-font-size: 11;"); - table.setPrefHeight(300); + private HBox createFilterChips() { + ToggleGroup group = new ToggleGroup(); - // ID column - TableColumn, String> idCol = new TableColumn<>("ID"); - idCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("id"); - return new javafx.beans.property.SimpleObjectProperty<>(String.valueOf(value)); - }); - idCol.setPrefWidth(50); + ToggleButton allBtn = chip("Todos", null, group, true); + ToggleButton productsBtn = chip("Productos", "PRODUCT_PUSH", group, false); + ToggleButton stockBtn = chip("Stock", "STOCK_PUSH", group, false); + ToggleButton ordersBtn = chip("Pedidos", "ORDER_PULL", group, false); - // Type column - TableColumn, String> typeCol = new TableColumn<>("Type"); - typeCol.setCellValueFactory(cf -> { - String value = (String) cf.getValue().get("syncType"); - return new javafx.beans.property.SimpleObjectProperty<>(value); - }); - typeCol.setPrefWidth(120); - - // Started At column - TableColumn, String> startedCol = new TableColumn<>("Started At"); - startedCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("startedAt"); - return new javafx.beans.property.SimpleObjectProperty<>( - value != null ? String.valueOf(value).substring(0, 19) : "—" - ); - }); - startedCol.setPrefWidth(150); - - // Finished At column - TableColumn, String> finishedCol = new TableColumn<>("Finished At"); - finishedCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("finishedAt"); - return new javafx.beans.property.SimpleObjectProperty<>( - value != null ? String.valueOf(value).substring(0, 19) : "—" - ); - }); - finishedCol.setPrefWidth(150); - - // Processed column - TableColumn, String> processedCol = new TableColumn<>("Processed"); - processedCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("itemsProcessed"); - return new javafx.beans.property.SimpleObjectProperty<>(String.valueOf(value)); - }); - processedCol.setPrefWidth(80); - - // Failed column - TableColumn, String> failedCol = new TableColumn<>("Failed"); - failedCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("itemsFailed"); - return new javafx.beans.property.SimpleObjectProperty<>(String.valueOf(value)); - }); - failedCol.setPrefWidth(80); - failedCol.setCellFactory(col -> new TableCell, String>() { - @Override - protected void updateItem(String item, boolean empty) { - super.updateItem(item, empty); - if (empty || item == null || "0".equals(item)) { - setText(item); - setStyle(""); - } else { - setText(item); - setStyle("-fx-text-fill: #c62828; -fx-font-weight: bold;"); - } - } + group.selectedToggleProperty().addListener((obs, old, newToggle) -> { + if (newToggle == null) { allBtn.setSelected(true); return; } + loadLogs((String) newToggle.getUserData()); }); - table.getColumns().addAll(idCol, typeCol, startedCol, finishedCol, processedCol, failedCol); - - return table; - } - - private VBox createDetailsPanel() { - VBox panel = new VBox(10); - panel.setPadding(new Insets(10)); - panel.setStyle("-fx-background-color: white; -fx-border-color: #e0e0e0;"); - - Label label = new Label("Error Details (click a row to see details):"); - label.setStyle("-fx-font-weight: bold;"); - - errorDetailsArea = new TextArea(); - errorDetailsArea.setEditable(false); - errorDetailsArea.setWrapText(true); - errorDetailsArea.setPrefHeight(120); - errorDetailsArea.setStyle("-fx-font-size: 10; -fx-control-inner-background: #f5f5f5;"); - - panel.getChildren().addAll(label, errorDetailsArea); - - return panel; - } - - private HBox createFilterBox() { - HBox box = new HBox(10); - box.setPadding(new Insets(10)); - box.setStyle("-fx-background-color: white; -fx-border-color: #e0e0e0;"); + HBox box = new HBox(8, allBtn, productsBtn, stockBtn, ordersBtn); box.setAlignment(Pos.CENTER_LEFT); - - Label filterLabel = new Label("Filter by type:"); - - ComboBox typeCombo = new ComboBox<>(); - typeCombo.setItems(FXCollections.observableArrayList( - "All", "PRODUCT_PUSH", "STOCK_PUSH", "ORDER_PULL" - )); - typeCombo.setValue("All"); - typeCombo.setPrefWidth(140); - - typeCombo.setOnAction(e -> { - String selected = typeCombo.getValue(); - loadLogs("All".equals(selected) ? null : selected); - }); - - Region spacer = new Region(); - HBox.setHgrow(spacer, javafx.scene.layout.Priority.ALWAYS); - - Button refreshButton = new Button("🔄 Refresh"); - refreshButton.setStyle("-fx-font-size: 11; -fx-padding: 8;"); - refreshButton.setOnAction(e -> loadLogs(null)); - - box.getChildren().addAll(filterLabel, typeCombo, spacer, refreshButton); - return box; } - private void loadLogs(String type) { - Thread thread = new Thread(() -> { - try { - SyncServiceClient client = sessionManager.getSyncServiceClient(); - List> logs = client.getLogs(type); + private ToggleButton chip(String text, String value, ToggleGroup group, boolean selected) { + ToggleButton btn = new ToggleButton(text); + btn.setToggleGroup(group); + btn.setSelected(selected); + btn.setUserData(value); + btn.getStyleClass().add("filter-chip"); + return btn; + } - ObservableList> items = FXCollections.observableArrayList(logs); - Platform.runLater(() -> logsTable.setItems(items)); - } catch (Exception e) { - log.error("Error loading logs: {}", e.getMessage()); - Platform.runLater(() -> AlertUtil.showError("Error", "Failed to load logs: " + e.getMessage())); + private TableView createTable() { + TableView table = new TableView<>(); + table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS); + table.setStyle("-fx-cursor: hand;"); + + // Status icon column + TableColumn iconCol = new TableColumn<>(""); + iconCol.setCellValueFactory(cf -> new javafx.beans.property.SimpleObjectProperty<>(cf.getValue())); + iconCol.setPrefWidth(36); + iconCol.setCellFactory(c -> new TableCell<>() { + @Override + protected void updateItem(SyncLogResponse item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { setGraphic(null); return; } + int failed = item.getItemsFailed() != null ? item.getItemsFailed() : 0; + boolean inP = item.getFinishedAt() == null; + String icon = inP ? "⏱" : (failed > 0 ? "✗" : "✓"); + String color = inP ? "#1565C0" : (failed > 0 ? "#C62828" : "#2E7D32"); + Label lbl = new Label(icon); + lbl.setStyle("-fx-font-size: 14; -fx-text-fill: " + color + ";"); + setGraphic(lbl); + setText(null); } }); - thread.setDaemon(true); - thread.start(); + + // Type + TableColumn typeCol = new TableColumn<>("Tipo"); + typeCol.setCellValueFactory(cf -> new SimpleStringProperty(DateUtils.syncTypeLabel(cf.getValue().getSyncType()))); + typeCol.setPrefWidth(100); + + // Started (relative) + TableColumn startedCol = new TableColumn<>("Inicio"); + startedCol.setCellValueFactory(cf -> new SimpleStringProperty(DateUtils.formatRelative(cf.getValue().getStartedAt()))); + startedCol.setPrefWidth(130); + + // Duration + TableColumn durationCol = new TableColumn<>("Duración"); + durationCol.setCellValueFactory(cf -> + new SimpleStringProperty(DateUtils.formatDuration(cf.getValue().getStartedAt(), cf.getValue().getFinishedAt()))); + durationCol.setPrefWidth(80); + + // Processed + TableColumn processedCol = new TableColumn<>("Procesados"); + processedCol.setCellValueFactory(cf -> { + Integer v = cf.getValue().getItemsProcessed(); + return new SimpleStringProperty(v != null ? String.valueOf(v) : "—"); + }); + processedCol.setPrefWidth(90); + + // Failed (red if > 0) + TableColumn failedCol = new TableColumn<>("Fallidos"); + failedCol.setCellValueFactory(cf -> { + Integer v = cf.getValue().getItemsFailed(); + return new SimpleStringProperty(v != null ? String.valueOf(v) : "—"); + }); + failedCol.setPrefWidth(80); + failedCol.setCellFactory(c -> new TableCell<>() { + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { setText(null); setStyle(""); return; } + setText(item); + boolean isFailed = !"—".equals(item) && !"0".equals(item); + setStyle(isFailed ? "-fx-text-fill: #C62828; -fx-font-weight: bold;" : ""); + } + }); + + table.getColumns().addAll(iconCol, typeCol, startedCol, durationCol, processedCol, failedCol); + return table; } - private void showLogDetails(Map log) { - String errorDetails = (String) log.get("errorDetails"); - if (errorDetails != null && !errorDetails.isEmpty()) { - errorDetailsArea.setText(errorDetails); - } else { - int processed = ((Number) log.get("itemsProcessed")).intValue(); - int failed = ((Number) log.get("itemsFailed")).intValue(); - errorDetailsArea.setText("✓ Sync completed successfully\n" + - "Processed: " + processed + "\n" + - "Failed: " + failed); - } + private void loadLogs(String type) { + Thread t = new Thread(() -> { + try { + SyncServiceClient client = sessionManager.getSyncServiceClient(); + List logs = client.getLogs(type); + Platform.runLater(() -> logsTable.setItems(FXCollections.observableArrayList(logs))); + } catch (Exception e) { + log.error("Error loading logs: {}", e.getMessage()); + Platform.runLater(() -> AlertUtil.showError("Error", "No se pudieron cargar los logs: " + e.getMessage())); + } + }); + t.setDaemon(true); + t.start(); } -} \ No newline at end of file +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/OrdersController.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/OrdersController.java index 1f17b0d..2140312 100644 --- a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/OrdersController.java +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/OrdersController.java @@ -1,30 +1,32 @@ package com.teterialosjuanjos.tfg.javafxclient.ui.controllers; import com.teterialosjuanjos.tfg.javafxclient.api.SyncServiceClient; +import com.teterialosjuanjos.tfg.javafxclient.model.OrderMappingResponse; import com.teterialosjuanjos.tfg.javafxclient.model.SessionManager; +import com.teterialosjuanjos.tfg.javafxclient.model.SyncTriggerResponse; import com.teterialosjuanjos.tfg.javafxclient.util.AlertUtil; +import com.teterialosjuanjos.tfg.javafxclient.util.DateUtils; import javafx.application.Platform; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.*; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import javafx.scene.layout.*; import lombok.extern.slf4j.Slf4j; import java.util.List; -import java.util.Map; /** - * Tab para pedidos: ver mapeos de PrestaShop → Dolibarr y disparar sync. + * Tab de mapeos de pedidos: PrestaShop → Dolibarr. + * Equivalente a MappingsScreen (tab Pedidos) de la app Android. */ @Slf4j public class OrdersController { private final SessionManager sessionManager; private final DashboardController dashboardController; - private TableView> ordersTable; + private TableView ordersTable; public OrdersController(SessionManager sessionManager, DashboardController dashboardController) { this.sessionManager = sessionManager; @@ -32,194 +34,145 @@ public class OrdersController { } public VBox createView() { - VBox root = new VBox(15); + VBox root = new VBox(12); root.setPadding(new Insets(20)); - root.setStyle("-fx-background-color: #fafafa;"); + root.setStyle("-fx-background-color: #FAFAFA;"); - // Title - Label title = new Label("Order Mappings (PrestaShop → Dolibarr)"); - title.setStyle("-fx-font-size: 18; -fx-font-weight: bold;"); + Label title = new Label("Mappings"); + title.setStyle("-fx-font-size: 20; -fx-font-weight: bold;"); + Label subtitle = new Label("Pedidos — PrestaShop → Dolibarr"); + subtitle.setStyle("-fx-font-size: 12; -fx-text-fill: #757575;"); - // Info box - HBox infoBox = createInfoBox(); + // Info banner + HBox infoBanner = new HBox(8); + infoBanner.getStyleClass().add("banner-info"); + Label infoLabel = new Label("ℹ Los pedidos de PrestaShop se importan en Dolibarr como comandas y facturas."); + infoLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #1565C0;"); + infoLabel.setWrapText(true); + infoBanner.getChildren().add(infoLabel); - // Table - ordersTable = createOrdersTable(); - VBox.setVgrow(ordersTable, javafx.scene.layout.Priority.ALWAYS); + ordersTable = createTable(); + VBox.setVgrow(ordersTable, Priority.ALWAYS); - // Action buttons HBox actionBox = createActionBox(); - root.getChildren().addAll(title, infoBox, ordersTable, actionBox); - - // Load initial data + root.getChildren().addAll(title, subtitle, infoBanner, ordersTable, actionBox); loadOrders(); - return root; } - private HBox createInfoBox() { - HBox box = new HBox(10); - box.setPadding(new Insets(10)); - box.setStyle("-fx-background-color: #e3f2fd; -fx-border-color: #1976d2; -fx-border-width: 1;"); - box.setAlignment(Pos.CENTER_LEFT); - - Label infoLabel = new Label( - "ℹ Orders from PrestaShop are automatically imported into Dolibarr as sales orders and invoices." - ); - infoLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #1565c0;"); - infoLabel.setWrapText(true); - - box.getChildren().add(infoLabel); - - return box; - } - - private TableView> createOrdersTable() { - TableView> table = new TableView<>(); + private TableView createTable() { + TableView table = new TableView<>(); table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS); - table.setStyle("-fx-font-size: 11;"); - // PrestaShop Order ID column - TableColumn, String> psOrderIdCol = new TableColumn<>("PS Order ID"); - psOrderIdCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("prestashopOrderId"); - return new javafx.beans.property.SimpleObjectProperty<>(String.valueOf(value)); - }); - psOrderIdCol.setPrefWidth(120); + TableColumn psIdCol = new TableColumn<>("Pedido PS #"); + psIdCol.setCellValueFactory(cf -> + new SimpleStringProperty(String.valueOf(cf.getValue().getPrestashopOrderId()))); + psIdCol.setPrefWidth(100); - // Dolibarr Order ID column - TableColumn, String> dolOrderIdCol = new TableColumn<>("Dolibarr Order ID"); - dolOrderIdCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("dolibarrOrderId"); - return new javafx.beans.property.SimpleObjectProperty<>( - value != null ? String.valueOf(value) : "—" - ); + TableColumn dolOrderCol = new TableColumn<>("Comanda Dolibarr"); + dolOrderCol.setCellValueFactory(cf -> { + Integer v = cf.getValue().getDolibarrOrderId(); + return new SimpleStringProperty(v != null ? String.valueOf(v) : "—"); }); - dolOrderIdCol.setPrefWidth(120); + dolOrderCol.setPrefWidth(130); - // Dolibarr Invoice ID column - TableColumn, String> dolInvoiceIdCol = new TableColumn<>("Invoice ID"); - dolInvoiceIdCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("dolibarrInvoiceId"); - return new javafx.beans.property.SimpleObjectProperty<>( - value != null ? String.valueOf(value) : "—" - ); + TableColumn dolInvoiceCol = new TableColumn<>("Factura"); + dolInvoiceCol.setCellValueFactory(cf -> { + Integer v = cf.getValue().getDolibarrInvoiceId(); + return new SimpleStringProperty(v != null ? String.valueOf(v) : "—"); }); - dolInvoiceIdCol.setPrefWidth(100); + dolInvoiceCol.setPrefWidth(80); - // Status column - TableColumn, String> statusCol = new TableColumn<>("Status"); - statusCol.setCellValueFactory(cf -> { - String value = (String) cf.getValue().get("status"); - return new javafx.beans.property.SimpleObjectProperty<>(value); - }); - statusCol.setPrefWidth(100); - statusCol.setCellFactory(col -> new TableCell, String>() { + // Status chip column + TableColumn statusCol = new TableColumn<>("Estado"); + statusCol.setCellValueFactory(cf -> new SimpleStringProperty(cf.getValue().getStatus())); + statusCol.setPrefWidth(110); + statusCol.setCellFactory(c -> new TableCell<>() { @Override - protected void updateItem(String item, boolean empty) { - super.updateItem(item, empty); - if (empty || item == null) { - setText(null); - setStyle(""); - } else { - setText(item); - if ("INVOICED".equals(item)) { - setStyle("-fx-text-fill: #2e7d32; -fx-font-weight: bold;"); - } - } + protected void updateItem(String status, boolean empty) { + super.updateItem(status, empty); + if (empty || status == null) { setGraphic(null); return; } + Label chip = new Label(DateUtils.orderStatusLabel(status)); + chip.getStyleClass().add(DateUtils.orderStatusCssClass(status)); + setGraphic(chip); + setText(null); } }); - // Imported At column - TableColumn, String> importedAtCol = new TableColumn<>("Imported At"); - importedAtCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("importedAt"); - return new javafx.beans.property.SimpleObjectProperty<>( - value != null ? String.valueOf(value).substring(0, 19) : "—" - ); - }); - importedAtCol.setPrefWidth(150); - - table.getColumns().addAll(psOrderIdCol, dolOrderIdCol, dolInvoiceIdCol, statusCol, importedAtCol); + TableColumn importedCol = new TableColumn<>("Importado"); + importedCol.setCellValueFactory(cf -> + new SimpleStringProperty(DateUtils.formatDateTime(cf.getValue().getImportedAt()))); + importedCol.setPrefWidth(150); + table.getColumns().addAll(psIdCol, dolOrderCol, dolInvoiceCol, statusCol, importedCol); return table; } private HBox createActionBox() { HBox box = new HBox(10); - box.setPadding(new Insets(15)); - box.setStyle("-fx-background-color: white; -fx-border-color: #e0e0e0;"); - box.setAlignment(Pos.CENTER_LEFT); + box.setPadding(new Insets(10, 0, 0, 0)); + box.setAlignment(Pos.CENTER_RIGHT); - Label label = new Label("Actions:"); - label.setStyle("-fx-font-weight: bold;"); + Button refreshBtn = new Button("🔄 Actualizar"); + refreshBtn.setStyle("-fx-background-color: #E0E0E0; -fx-background-radius: 8; -fx-cursor: hand; -fx-padding: 8 16;"); + refreshBtn.setOnAction(e -> loadOrders()); - Button syncButton = new Button("🔄 Sync Orders from PrestaShop"); - syncButton.setStyle("-fx-font-size: 12; -fx-padding: 10; -fx-background-color: #f57c00; -fx-text-fill: white;"); - syncButton.setOnAction(e -> triggerOrderSync()); - - Button refreshButton = new Button("🔄 Refresh"); - refreshButton.setStyle("-fx-font-size: 11; -fx-padding: 8;"); - refreshButton.setOnAction(e -> loadOrders()); - - box.getChildren().addAll(label, syncButton, refreshButton); + Button syncBtn = new Button("Sincronizar pedidos"); + syncBtn.setStyle("-fx-background-color: #E65100; -fx-text-fill: white; " + + "-fx-background-radius: 8; -fx-padding: 10 20; -fx-font-weight: bold; -fx-cursor: hand;"); + syncBtn.setOnAction(e -> triggerOrderSync(syncBtn)); + box.getChildren().addAll(refreshBtn, syncBtn); return box; } private void loadOrders() { - Thread thread = new Thread(() -> { + Thread t = new Thread(() -> { try { SyncServiceClient client = sessionManager.getSyncServiceClient(); - List> mappings = client.getOrderMappings(); - - ObservableList> items = FXCollections.observableArrayList(mappings); - Platform.runLater(() -> ordersTable.setItems(items)); + List orders = client.getOrderMappings(); + Platform.runLater(() -> ordersTable.setItems(FXCollections.observableArrayList(orders))); } catch (Exception e) { log.error("Error loading orders: {}", e.getMessage()); - Platform.runLater(() -> AlertUtil.showError("Error", "Failed to load orders: " + e.getMessage())); + Platform.runLater(() -> AlertUtil.showError("Error", "No se pudieron cargar los pedidos: " + e.getMessage())); } }); - thread.setDaemon(true); - thread.start(); + t.setDaemon(true); + t.start(); } - private void triggerOrderSync() { - Alert confirmAlert = new Alert(Alert.AlertType.CONFIRMATION); - confirmAlert.setTitle("Confirm Sync"); - confirmAlert.setHeaderText("Sync Orders from PrestaShop to Dolibarr?"); - confirmAlert.setContentText("This will import new orders from PrestaShop as sales orders and invoices in Dolibarr."); + private void triggerOrderSync(Button btn) { + Alert confirm = new Alert(Alert.AlertType.CONFIRMATION); + confirm.setTitle("Confirmar sincronización"); + confirm.setHeaderText("¿Sincronizar pedidos de PrestaShop a Dolibarr?"); + confirm.setContentText("Se importarán los pedidos nuevos como comandas y facturas en Dolibarr."); + if (confirm.showAndWait().orElse(ButtonType.CANCEL) != ButtonType.OK) return; - if (confirmAlert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) { - Thread thread = new Thread(() -> { - try { - SyncServiceClient client = sessionManager.getSyncServiceClient(); - Map result = client.triggerOrderSync(); - - Platform.runLater(() -> { - int processed = ((Number) result.get("processed")).intValue(); - int failed = ((Number) result.get("failed")).intValue(); - - if (failed == 0) { - AlertUtil.showInfo("Success", - "Orders synced: " + processed + " orders imported"); - } else { - AlertUtil.showWarn("Partial Success", - "Processed: " + processed + ", Failed: " + failed); - } - - loadOrders(); - dashboardController.refreshDashboard(); - }); - } catch (Exception e) { - Platform.runLater(() -> - AlertUtil.showError("Sync Error", e.getMessage()) - ); - } - }); - thread.setDaemon(true); - thread.start(); - } + btn.setDisable(true); + Thread t = new Thread(() -> { + try { + SyncServiceClient client = sessionManager.getSyncServiceClient(); + SyncTriggerResponse result = client.triggerOrderSync(); + Platform.runLater(() -> { + btn.setDisable(false); + if (result.getItemsFailed() == 0) { + AlertUtil.showInfo("Completado", "Pedidos importados: " + result.getItemsProcessed()); + } else { + AlertUtil.showWarn("Completado con errores", + result.getItemsProcessed() + " procesados, " + result.getItemsFailed() + " fallidos."); + } + loadOrders(); + dashboardController.refreshDashboard(); + }); + } catch (Exception e) { + Platform.runLater(() -> { + btn.setDisable(false); + AlertUtil.showError("Error de sincronización", e.getMessage()); + }); + } + }); + t.setDaemon(true); + t.start(); } -} \ No newline at end of file +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController.java index 02df466..c3d107c 100644 --- a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController.java +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController.java @@ -1,30 +1,37 @@ package com.teterialosjuanjos.tfg.javafxclient.ui.controllers; import com.teterialosjuanjos.tfg.javafxclient.api.SyncServiceClient; +import com.teterialosjuanjos.tfg.javafxclient.model.ProductMappingResponse; import com.teterialosjuanjos.tfg.javafxclient.model.SessionManager; +import com.teterialosjuanjos.tfg.javafxclient.model.SyncTriggerResponse; import com.teterialosjuanjos.tfg.javafxclient.util.AlertUtil; +import com.teterialosjuanjos.tfg.javafxclient.util.DateUtils; import javafx.application.Platform; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.*; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import javafx.scene.layout.VBox; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import javafx.scene.layout.*; import lombok.extern.slf4j.Slf4j; -import java.util.*; +import java.util.List; +import java.util.stream.Collectors; /** - * Tab para productos: ver mapeos y disparar sync. + * Tab de mapeos de productos con búsqueda por SKU y filtros de estado. + * Equivalente a MappingsScreen (tab Productos) de la app Android. */ @Slf4j public class ProductsController { private final SessionManager sessionManager; private final DashboardController dashboardController; - private TableView> productsTable; + + private TableView productsTable; + private List allProducts = List.of(); + private String activeStatusFilter = null; public ProductsController(SessionManager sessionManager, DashboardController dashboardController) { this.sessionManager = sessionManager; @@ -32,240 +39,197 @@ public class ProductsController { } public VBox createView() { - VBox root = new VBox(15); + VBox root = new VBox(12); root.setPadding(new Insets(20)); - root.setStyle("-fx-background-color: #fafafa;"); + root.setStyle("-fx-background-color: #FAFAFA;"); - // Title - Label title = new Label("Product Mappings"); - title.setStyle("-fx-font-size: 18; -fx-font-weight: bold;"); + // Header + Label title = new Label("Mappings"); + title.setStyle("-fx-font-size: 20; -fx-font-weight: bold;"); + Label subtitle = new Label("Productos — Dolibarr ↔ PrestaShop"); + subtitle.setStyle("-fx-font-size: 12; -fx-text-fill: #757575;"); - // Filter & Actions - HBox filterBox = createFilterBox(); + // Search bar + TextField searchField = new TextField(); + searchField.setPromptText("Buscar por SKU..."); + searchField.setMaxWidth(Double.MAX_VALUE); + searchField.textProperty().addListener((obs, old, val) -> applyFilters(searchField, val)); + + // Filter chips + HBox chipsBox = createFilterChips(searchField); // Table - productsTable = createProductsTable(); - VBox.setVgrow(productsTable, javafx.scene.layout.Priority.ALWAYS); + productsTable = createTable(); + VBox.setVgrow(productsTable, Priority.ALWAYS); // Action buttons HBox actionBox = createActionBox(); - root.getChildren().addAll(title, filterBox, productsTable, actionBox); - - // Load initial data - loadProducts(null); + root.getChildren().addAll(title, subtitle, searchField, chipsBox, productsTable, actionBox); + loadProducts(); return root; } - private HBox createFilterBox() { - HBox box = new HBox(10); - box.setPadding(new Insets(10)); - box.setStyle("-fx-background-color: white; -fx-border-color: #e0e0e0;"); - box.setAlignment(Pos.CENTER_LEFT); + private HBox createFilterChips(TextField searchField) { + ToggleGroup group = new ToggleGroup(); - Label filterLabel = new Label("Filter by status:"); + ToggleButton allBtn = chip("Todos", null, group, true); + ToggleButton syncedBtn = chip("Sincronizados", "SYNCED", group, false); + ToggleButton pendingBtn = chip("Pendientes", "PENDING", group, false); + ToggleButton errorBtn = chip("Con error", "ERROR", group, false); - ComboBox statusCombo = new ComboBox<>(); - statusCombo.setItems(FXCollections.observableArrayList( - "All", "SYNCED", "PENDING", "ERROR" - )); - statusCombo.setValue("All"); - statusCombo.setPrefWidth(120); - - statusCombo.setOnAction(e -> { - String selected = statusCombo.getValue(); - loadProducts("All".equals(selected) ? null : selected); + group.selectedToggleProperty().addListener((obs, old, newToggle) -> { + if (newToggle == null) { allBtn.setSelected(true); return; } + activeStatusFilter = (String) newToggle.getUserData(); + loadProducts(); }); - Region spacer = new Region(); - HBox.setHgrow(spacer, javafx.scene.layout.Priority.ALWAYS); - - Button refreshButton = new Button("🔄 Refresh"); - refreshButton.setStyle("-fx-font-size: 11; -fx-padding: 8;"); - refreshButton.setOnAction(e -> loadProducts(null)); - - box.getChildren().addAll(filterLabel, statusCombo, spacer, refreshButton); - + HBox box = new HBox(8, allBtn, syncedBtn, pendingBtn, errorBtn); + box.setAlignment(Pos.CENTER_LEFT); return box; } - private TableView> createProductsTable() { - TableView> table = new TableView<>(); + private ToggleButton chip(String text, String value, ToggleGroup group, boolean selected) { + ToggleButton btn = new ToggleButton(text); + btn.setToggleGroup(group); + btn.setSelected(selected); + btn.setUserData(value); + btn.getStyleClass().add("filter-chip"); + return btn; + } + + private TableView createTable() { + TableView table = new TableView<>(); table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS); - table.setStyle("-fx-font-size: 11;"); - // SKU column - TableColumn, String> skuCol = new TableColumn<>("SKU"); - skuCol.setCellValueFactory(cf -> { - String value = (String) cf.getValue().get("sku"); - return new javafx.beans.property.SimpleObjectProperty<>(value); - }); - skuCol.setPrefWidth(120); + TableColumn skuCol = col("SKU", p -> p.getSku()); + skuCol.setPrefWidth(140); - // Dolibarr ID column - TableColumn, String> dolIdCol = new TableColumn<>("Dolibarr ID"); - dolIdCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("dolibarrId"); - return new javafx.beans.property.SimpleObjectProperty<>(String.valueOf(value)); - }); + TableColumn dolIdCol = col("Dolibarr ID", + p -> p.getDolibarrId() != null ? String.valueOf(p.getDolibarrId()) : "—"); dolIdCol.setPrefWidth(100); - // PrestaShop ID column - TableColumn, String> psIdCol = new TableColumn<>("PrestaShop ID"); - psIdCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("prestashopId"); - return new javafx.beans.property.SimpleObjectProperty<>(String.valueOf(value)); - }); - psIdCol.setPrefWidth(100); + TableColumn psIdCol = col("PrestaShop ID", + p -> p.getPrestashopId() != null ? String.valueOf(p.getPrestashopId()) : "—"); + psIdCol.setPrefWidth(110); - // Status column - TableColumn, String> statusCol = new TableColumn<>("Status"); - statusCol.setCellValueFactory(cf -> { - String value = (String) cf.getValue().get("syncStatus"); - return new javafx.beans.property.SimpleObjectProperty<>(value); - }); - statusCol.setPrefWidth(80); - statusCol.setCellFactory(col -> new TableCell, String>() { + TableColumn lastSyncCol = col("Última sync", + p -> DateUtils.formatRelative(p.getLastSyncedAt())); + lastSyncCol.setPrefWidth(130); + + // Status chip column + TableColumn statusCol = new TableColumn<>("Estado"); + statusCol.setCellValueFactory(cf -> new SimpleStringProperty(cf.getValue().getSyncStatus())); + statusCol.setPrefWidth(120); + statusCol.setCellFactory(c -> new TableCell<>() { @Override - protected void updateItem(String item, boolean empty) { - super.updateItem(item, empty); - if (empty || item == null) { - setText(null); - setStyle(""); - } else { - setText(item); - switch (item) { - case "SYNCED" -> setStyle("-fx-text-fill: #2e7d32; -fx-font-weight: bold;"); - case "PENDING" -> setStyle("-fx-text-fill: #f57f17; -fx-font-weight: bold;"); - case "ERROR" -> setStyle("-fx-text-fill: #c62828; -fx-font-weight: bold;"); - } - } + protected void updateItem(String status, boolean empty) { + super.updateItem(status, empty); + if (empty || status == null) { setGraphic(null); return; } + Label chip = new Label(DateUtils.productStatusLabel(status)); + chip.getStyleClass().add(DateUtils.productStatusCssClass(status)); + setGraphic(chip); + setText(null); } }); - // Error message column - TableColumn, String> errorCol = new TableColumn<>("Error"); - errorCol.setCellValueFactory(cf -> { - Object value = cf.getValue().get("errorMessage"); - String text = value != null ? (String) value : ""; - return new javafx.beans.property.SimpleObjectProperty<>(text); - }); + TableColumn errorCol = col("Error", + p -> p.getErrorMessage() != null ? p.getErrorMessage() : ""); errorCol.setPrefWidth(200); - table.getColumns().addAll(skuCol, dolIdCol, psIdCol, statusCol, errorCol); - + table.getColumns().addAll(skuCol, dolIdCol, psIdCol, lastSyncCol, statusCol, errorCol); return table; } + private TableColumn col( + String header, java.util.function.Function extractor) { + TableColumn col = new TableColumn<>(header); + col.setCellValueFactory(cf -> new SimpleStringProperty(extractor.apply(cf.getValue()))); + return col; + } + private HBox createActionBox() { HBox box = new HBox(10); - box.setPadding(new Insets(15)); - box.setStyle("-fx-background-color: white; -fx-border-color: #e0e0e0;"); + box.setPadding(new Insets(10, 0, 0, 0)); box.setAlignment(Pos.CENTER_RIGHT); - Button syncButton = new Button("🔄 Sync Products from Dolibarr"); - syncButton.setStyle("-fx-font-size: 12; -fx-padding: 10; -fx-background-color: #1976d2; -fx-text-fill: white;"); - syncButton.setOnAction(e -> triggerProductSync()); + Button refreshBtn = new Button("🔄 Actualizar"); + refreshBtn.setStyle("-fx-background-color: #E0E0E0; -fx-background-radius: 8; -fx-cursor: hand; -fx-padding: 8 16;"); + refreshBtn.setOnAction(e -> loadProducts()); - Button stockButton = new Button("📊 Sync Stock"); - stockButton.setStyle("-fx-font-size: 12; -fx-padding: 10; -fx-background-color: #388e3c; -fx-text-fill: white;"); - stockButton.setOnAction(e -> triggerStockSync()); + Button syncProductsBtn = new Button("Sincronizar productos"); + syncProductsBtn.setStyle("-fx-background-color: #1565C0; -fx-text-fill: white; " + + "-fx-background-radius: 8; -fx-padding: 10 20; -fx-font-weight: bold; -fx-cursor: hand;"); + syncProductsBtn.setOnAction(e -> triggerSync("PRODUCT_PUSH", syncProductsBtn)); - box.getChildren().addAll(syncButton, stockButton); + Button syncStockBtn = new Button("Sincronizar stock"); + syncStockBtn.setStyle("-fx-background-color: #2E7D32; -fx-text-fill: white; " + + "-fx-background-radius: 8; -fx-padding: 10 20; -fx-cursor: hand;"); + syncStockBtn.setOnAction(e -> triggerSync("STOCK_PUSH", syncStockBtn)); + box.getChildren().addAll(refreshBtn, syncProductsBtn, syncStockBtn); return box; } - private void loadProducts(String status) { - Thread thread = new Thread(() -> { + private void loadProducts() { + Thread t = new Thread(() -> { try { SyncServiceClient client = sessionManager.getSyncServiceClient(); - List> mappings = client.getProductMappings(status); - - ObservableList> items = FXCollections.observableArrayList(mappings); - Platform.runLater(() -> productsTable.setItems(items)); + allProducts = client.getProductMappings(activeStatusFilter); + Platform.runLater(() -> productsTable.setItems(FXCollections.observableArrayList(allProducts))); } catch (Exception e) { log.error("Error loading products: {}", e.getMessage()); - Platform.runLater(() -> AlertUtil.showError("Error", "Failed to load products: " + e.getMessage())); + Platform.runLater(() -> AlertUtil.showError("Error", "No se pudieron cargar los productos: " + e.getMessage())); } }); - thread.setDaemon(true); - thread.start(); + t.setDaemon(true); + t.start(); } - private void triggerProductSync() { - Alert confirmAlert = new Alert(Alert.AlertType.CONFIRMATION); - confirmAlert.setTitle("Confirm Sync"); - confirmAlert.setHeaderText("Sync Products from Dolibarr to PrestaShop?"); - confirmAlert.setContentText("This will push new/modified products from Dolibarr to PrestaShop."); - - if (confirmAlert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) { - Thread thread = new Thread(() -> { - try { - SyncServiceClient client = sessionManager.getSyncServiceClient(); - Map result = client.triggerProductSync(); - - Platform.runLater(() -> { - int processed = ((Number) result.get("processed")).intValue(); - int failed = ((Number) result.get("failed")).intValue(); - - if (failed == 0) { - AlertUtil.showInfo("Success", - "Sync completed: " + processed + " products processed"); - } else { - AlertUtil.showWarn("Partial Success", - "Processed: " + processed + ", Failed: " + failed); - } - - loadProducts(null); - dashboardController.refreshDashboard(); - }); - } catch (Exception e) { - Platform.runLater(() -> - AlertUtil.showError("Sync Error", e.getMessage()) - ); - } - }); - thread.setDaemon(true); - thread.start(); - } + private void applyFilters(TextField searchField, String searchText) { + String query = searchText == null ? "" : searchText.toLowerCase(); + List filtered = allProducts.stream() + .filter(p -> p.getSku() != null && p.getSku().toLowerCase().contains(query)) + .collect(Collectors.toList()); + productsTable.setItems(FXCollections.observableArrayList(filtered)); } - private void triggerStockSync() { - Alert confirmAlert = new Alert(Alert.AlertType.CONFIRMATION); - confirmAlert.setTitle("Confirm Sync"); - confirmAlert.setHeaderText("Sync Stock from Dolibarr to PrestaShop?"); - confirmAlert.setContentText("This will update stock levels in PrestaShop from Dolibarr."); + private void triggerSync(String type, Button btn) { + String label = type.equals("PRODUCT_PUSH") ? "Productos" : "Stock"; + Alert confirm = new Alert(Alert.AlertType.CONFIRMATION); + confirm.setTitle("Confirmar sincronización"); + confirm.setHeaderText("¿Sincronizar " + label + "?"); + confirm.setContentText(DateUtils.syncTypeDirection(type)); + if (confirm.showAndWait().orElse(ButtonType.CANCEL) != ButtonType.OK) return; - if (confirmAlert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) { - Thread thread = new Thread(() -> { - try { - SyncServiceClient client = sessionManager.getSyncServiceClient(); - Map result = client.triggerStockSync(); + btn.setDisable(true); + Thread t = new Thread(() -> { + try { + SyncServiceClient client = sessionManager.getSyncServiceClient(); + SyncTriggerResponse result = type.equals("PRODUCT_PUSH") + ? client.triggerProductSync() + : client.triggerStockSync(); - Platform.runLater(() -> { - int processed = ((Number) result.get("processed")).intValue(); - int failed = ((Number) result.get("failed")).intValue(); - - if (failed == 0) { - AlertUtil.showInfo("Success", - "Stock sync completed: " + processed + " items updated"); - } else { - AlertUtil.showWarn("Partial Success", - "Processed: " + processed + ", Failed: " + failed); - } - - dashboardController.refreshDashboard(); - }); - } catch (Exception e) { - Platform.runLater(() -> - AlertUtil.showError("Sync Error", e.getMessage()) - ); - } - }); - thread.setDaemon(true); - thread.start(); - } + Platform.runLater(() -> { + btn.setDisable(false); + if (result.getItemsFailed() == 0) { + AlertUtil.showInfo("Completado", label + ": " + result.getItemsProcessed() + " procesados."); + } else { + AlertUtil.showWarn("Completado con errores", + result.getItemsProcessed() + " procesados, " + result.getItemsFailed() + " fallidos."); + } + loadProducts(); + dashboardController.refreshDashboard(); + }); + } catch (Exception e) { + Platform.runLater(() -> { + btn.setDisable(false); + AlertUtil.showError("Error de sincronización", e.getMessage()); + }); + } + }); + t.setDaemon(true); + t.start(); } -} \ No newline at end of file +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/SettingsController.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/SettingsController.java new file mode 100644 index 0000000..e6fcdca --- /dev/null +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/SettingsController.java @@ -0,0 +1,168 @@ +package com.teterialosjuanjos.tfg.javafxclient.ui.controllers; + +import com.teterialosjuanjos.tfg.javafxclient.model.SessionManager; +import com.teterialosjuanjos.tfg.javafxclient.util.AlertUtil; +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.layout.*; +import lombok.extern.slf4j.Slf4j; + +/** + * Tab de ajustes de conexión. + * Permite cambiar la URL del servidor, usuario y contraseña en caliente. + * Equivalente a SettingsScreen de la app Android. + */ +@Slf4j +public class SettingsController { + + private final SessionManager sessionManager; + + public SettingsController(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + public ScrollPane createView() { + VBox root = new VBox(20); + root.setPadding(new Insets(24)); + root.setStyle("-fx-background-color: #FAFAFA;"); + root.setMaxWidth(640); + + Label title = new Label("Ajustes de conexión"); + title.setStyle("-fx-font-size: 20; -fx-font-weight: bold;"); + Label subtitle = new Label("Configura el servidor sync-service"); + subtitle.setStyle("-fx-font-size: 12; -fx-text-fill: #757575;"); + + // Form card + VBox formCard = new VBox(14); + formCard.setPadding(new Insets(20)); + formCard.setStyle( + "-fx-background-color: white;" + + "-fx-background-radius: 12;" + + "-fx-border-radius: 12;" + + "-fx-border-color: #E0E0E0;" + + "-fx-border-width: 1;" + ); + + // URL + Label urlLabel = new Label("URL del servidor"); + urlLabel.setStyle("-fx-font-size: 11; -fx-font-weight: bold; -fx-text-fill: #546E7A;"); + TextField urlField = new TextField(sessionManager.getBaseUrl()); + urlField.setPromptText("https://tu-servidor.up.railway.app/"); + Label urlHelp = new Label("Incluye la barra final /"); + urlHelp.setStyle("-fx-font-size: 10; -fx-text-fill: #9E9E9E;"); + + // Username + Label userLabel = new Label("Usuario"); + userLabel.setStyle("-fx-font-size: 11; -fx-font-weight: bold; -fx-text-fill: #546E7A;"); + TextField userField = new TextField( + sessionManager.getUsername() != null ? sessionManager.getUsername() : "admin"); + userField.setPromptText("admin"); + + // Password with visibility toggle + Label passLabel = new Label("Contraseña"); + passLabel.setStyle("-fx-font-size: 11; -fx-font-weight: bold; -fx-text-fill: #546E7A;"); + PasswordField passField = new PasswordField(); + passField.setText(sessionManager.getPassword() != null ? sessionManager.getPassword() : ""); + TextField passVisible = new TextField(); + passVisible.setManaged(false); + passVisible.setVisible(false); + Button toggleBtn = new Button("👁"); + toggleBtn.setStyle("-fx-background-color: transparent; -fx-cursor: hand; -fx-padding: 4; -fx-border-width: 0;"); + toggleBtn.setOnAction(e -> { + boolean show = !passVisible.isVisible(); + if (show) passVisible.setText(passField.getText()); + else passField.setText(passVisible.getText()); + passVisible.setVisible(show); + passVisible.setManaged(show); + passField.setVisible(!show); + passField.setManaged(!show); + }); + HBox.setHgrow(passField, Priority.ALWAYS); + HBox.setHgrow(passVisible, Priority.ALWAYS); + HBox passBox = new HBox(6, passField, passVisible, toggleBtn); + passBox.setAlignment(Pos.CENTER_LEFT); + + formCard.getChildren().addAll( + urlLabel, urlField, urlHelp, + new Separator(), + userLabel, userField, + passLabel, passBox + ); + + // Test result label + Label testResultLabel = new Label(); + testResultLabel.setWrapText(true); + testResultLabel.setVisible(false); + + // Buttons + HBox btnBox = new HBox(12); + btnBox.setAlignment(Pos.CENTER_RIGHT); + + Button testBtn = new Button("Probar conexión"); + testBtn.setStyle("-fx-background-color: #546E7A; -fx-text-fill: white; " + + "-fx-background-radius: 8; -fx-padding: 10 20; -fx-cursor: hand;"); + + Button saveBtn = new Button("Guardar"); + saveBtn.setStyle("-fx-background-color: #1565C0; -fx-text-fill: white; " + + "-fx-background-radius: 8; -fx-padding: 10 20; -fx-font-weight: bold; -fx-cursor: hand;"); + + testBtn.setOnAction(e -> handleTest(urlField, userField, passField, passVisible, testBtn, testResultLabel)); + saveBtn.setOnAction(e -> handleSave(urlField, userField, passField, passVisible, testResultLabel)); + + btnBox.getChildren().addAll(testBtn, saveBtn); + + root.getChildren().addAll(title, subtitle, formCard, testResultLabel, btnBox); + + ScrollPane scrollPane = new ScrollPane(root); + scrollPane.setFitToWidth(true); + return scrollPane; + } + + private void handleTest(TextField urlField, TextField userField, + PasswordField passField, TextField passVisible, + Button testBtn, Label resultLabel) { + String url = urlField.getText().trim(); + String user = userField.getText().trim(); + String pass = passField.isVisible() ? passField.getText() : passVisible.getText(); + + testBtn.setDisable(true); + resultLabel.setText("Probando conexión..."); + resultLabel.setStyle("-fx-text-fill: #546E7A;"); + resultLabel.setVisible(true); + + Thread t = new Thread(() -> { + boolean ok = sessionManager.testConnection(url, user, pass); + Platform.runLater(() -> { + testBtn.setDisable(false); + if (ok) { + resultLabel.setText("✓ Conexión exitosa"); + resultLabel.setStyle("-fx-text-fill: #2E7D32; -fx-font-weight: bold;"); + } else { + resultLabel.setText("✗ No se pudo conectar. Verifica la URL y credenciales."); + resultLabel.setStyle("-fx-text-fill: #C62828; -fx-font-weight: bold;"); + } + }); + }); + t.setDaemon(true); + t.start(); + } + + private void handleSave(TextField urlField, TextField userField, + PasswordField passField, TextField passVisible, + Label resultLabel) { + String url = urlField.getText().trim(); + String user = userField.getText().trim(); + String pass = passField.isVisible() ? passField.getText() : passVisible.getText(); + + if (url.isEmpty() || user.isEmpty() || pass.isEmpty()) { + AlertUtil.showError("Validación", "Todos los campos son obligatorios."); + return; + } + + sessionManager.updateSettings(url, user, pass); + resultLabel.setVisible(false); + AlertUtil.showInfo("Ajustes guardados", "Los ajustes de conexión se han guardado correctamente."); + } +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/dialogs/LogDetailDialog.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/dialogs/LogDetailDialog.java new file mode 100644 index 0000000..f9e4fff --- /dev/null +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/dialogs/LogDetailDialog.java @@ -0,0 +1,154 @@ +package com.teterialosjuanjos.tfg.javafxclient.ui.dialogs; + +import com.teterialosjuanjos.tfg.javafxclient.model.SyncLogResponse; +import com.teterialosjuanjos.tfg.javafxclient.util.DateUtils; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.layout.*; +import javafx.stage.Modality; +import javafx.stage.Stage; + +/** + * Diálogo modal con el detalle de un registro de sincronización. + * Equivalente a LogDetailScreen de la app Android. + */ +public class LogDetailDialog { + + private final SyncLogResponse log; + + public LogDetailDialog(SyncLogResponse log) { + this.log = log; + } + + public void show() { + Stage stage = new Stage(); + stage.initModality(Modality.APPLICATION_MODAL); + stage.setTitle("Detalle del log #" + log.getId()); + stage.setWidth(580); + stage.setResizable(true); + + VBox root = new VBox(16); + root.setPadding(new Insets(24)); + root.setStyle("-fx-background-color: #FAFAFA;"); + + root.getChildren().add(createStatusBanner()); + root.getChildren().add(new Separator()); + root.getChildren().add(createInfoSection()); + root.getChildren().add(createResultSection()); + + if (log.getErrorDetails() != null && !log.getErrorDetails().isBlank()) { + root.getChildren().add(createErrorSection()); + } + + Button closeBtn = new Button("Cerrar"); + closeBtn.setStyle("-fx-background-color: #1565C0; -fx-text-fill: white; " + + "-fx-background-radius: 8; -fx-padding: 8 24; -fx-font-weight: bold; -fx-cursor: hand;"); + closeBtn.setOnAction(e -> stage.close()); + + HBox btnBox = new HBox(closeBtn); + btnBox.setAlignment(Pos.CENTER_RIGHT); + root.getChildren().add(btnBox); + + ScrollPane scrollPane = new ScrollPane(root); + scrollPane.setFitToWidth(true); + scrollPane.setStyle("-fx-background-color: #FAFAFA;"); + + Scene scene = new Scene(scrollPane); + try { + scene.getStylesheets().add(getClass().getResource("/styles.css").toExternalForm()); + } catch (Exception ignored) {} + + stage.setScene(scene); + stage.show(); + } + + private VBox createStatusBanner() { + int failed = log.getItemsFailed() != null ? log.getItemsFailed() : 0; + boolean inProgress = log.getFinishedAt() == null; + + String icon, statusText, cssClass; + if (inProgress) { + icon = "⏱"; statusText = "En curso"; cssClass = "banner-info"; + } else if (failed > 0) { + icon = "✗"; statusText = failed + " elemento(s) fallaron"; cssClass = "banner-error"; + } else { + icon = "✓"; statusText = "Completado sin errores"; cssClass = "banner-success"; + } + + Label statusLabel = new Label(icon + " " + statusText); + statusLabel.setStyle("-fx-font-size: 14; -fx-font-weight: bold;"); + + Label subtitleLabel = new Label( + DateUtils.syncTypeLabel(log.getSyncType()) + ": " + + DateUtils.syncTypeDirection(log.getSyncType()) + ); + subtitleLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #757575;"); + + VBox banner = new VBox(4, statusLabel, subtitleLabel); + banner.getStyleClass().add(cssClass); + return banner; + } + + private VBox createInfoSection() { + Label sectionTitle = new Label("Información"); + sectionTitle.setStyle("-fx-font-size: 13; -fx-font-weight: bold; -fx-text-fill: #546E7A;"); + + GridPane grid = new GridPane(); + grid.setHgap(20); + grid.setVgap(8); + + addRow(grid, 0, "Tipo de sync", DateUtils.syncTypeLabel(log.getSyncType())); + addRow(grid, 1, "Inicio", DateUtils.formatDateTime(log.getStartedAt())); + addRow(grid, 2, "Fin", log.getFinishedAt() != null ? DateUtils.formatDateTime(log.getFinishedAt()) : "En curso"); + addRow(grid, 3, "Duración", DateUtils.formatDuration(log.getStartedAt(), log.getFinishedAt())); + + return new VBox(8, sectionTitle, grid); + } + + private VBox createResultSection() { + Label sectionTitle = new Label("Resultado"); + sectionTitle.setStyle("-fx-font-size: 13; -fx-font-weight: bold; -fx-text-fill: #546E7A;"); + + int processed = log.getItemsProcessed() != null ? log.getItemsProcessed() : 0; + int failed = log.getItemsFailed() != null ? log.getItemsFailed() : 0; + + GridPane grid = new GridPane(); + grid.setHgap(20); + grid.setVgap(8); + + addRow(grid, 0, "Procesados correctamente", String.valueOf(processed)); + + Label failKey = new Label("Fallidos:"); + failKey.setStyle("-fx-text-fill: #757575; -fx-font-size: 11;"); + Label failVal = new Label(String.valueOf(failed)); + failVal.setStyle("-fx-font-weight: bold; -fx-font-size: 11;" + + (failed > 0 ? " -fx-text-fill: #C62828;" : "")); + grid.add(failKey, 0, 1); + grid.add(failVal, 1, 1); + + return new VBox(8, sectionTitle, grid); + } + + private VBox createErrorSection() { + Label sectionTitle = new Label("Detalle de errores"); + sectionTitle.setStyle("-fx-font-size: 13; -fx-font-weight: bold; -fx-text-fill: #C62828;"); + + TextArea errorArea = new TextArea(log.getErrorDetails()); + errorArea.setEditable(false); + errorArea.setWrapText(true); + errorArea.setPrefHeight(160); + + return new VBox(8, sectionTitle, errorArea); + } + + private void addRow(GridPane grid, int row, String key, String value) { + Label k = new Label(key + ":"); + k.setStyle("-fx-text-fill: #757575; -fx-font-size: 11;"); + Label v = new Label(value); + v.setStyle("-fx-font-weight: bold; -fx-font-size: 11;"); + grid.add(k, 0, row); + grid.add(v, 1, row); + } +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/stages/LoginStage.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/stages/LoginStage.java index d8b6ebe..bb93af3 100644 --- a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/stages/LoginStage.java +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/stages/LoginStage.java @@ -7,17 +7,20 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; -import javafx.scene.layout.Region; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import javafx.stage.Stage; import lombok.extern.slf4j.Slf4j; /** * Pantalla de login para autenticación contra el sync-service. + * Pre-rellena los campos con los ajustes guardados en SettingsStore. */ @Slf4j public class LoginStage { + private static final String PROD_URL = "https://proyectointermodular-production-a9c3.up.railway.app"; + private static final String DEV_URL = "http://localhost:8080"; + private final SessionManager sessionManager; private final Stage primaryStage; private Stage loginWindow; @@ -29,9 +32,9 @@ public class LoginStage { public void show() { loginWindow = new Stage(); - loginWindow.setTitle("TFG Sync Service — Login"); - loginWindow.setWidth(400); - loginWindow.setHeight(500); + loginWindow.setTitle("Sync Manager — Iniciar sesión"); + loginWindow.setWidth(420); + loginWindow.setHeight(520); loginWindow.setResizable(false); VBox root = createLoginForm(); @@ -45,129 +48,121 @@ public class LoginStage { } private VBox createLoginForm() { - VBox form = new VBox(15); - form.setPadding(new Insets(40)); - form.setAlignment(Pos.TOP_CENTER); - form.setStyle("-fx-border-color: #e0e0e0; -fx-border-width: 0;"); + VBox form = new VBox(0); + form.setStyle("-fx-background-color: #F5F5F5;"); + + // Top color bar + HBox topBar = new HBox(); + topBar.setMinHeight(6); + topBar.setStyle("-fx-background-color: #1565C0;"); + + // Card + VBox card = new VBox(14); + card.setPadding(new Insets(36, 40, 36, 40)); + card.setAlignment(Pos.TOP_CENTER); + card.setStyle( + "-fx-background-color: white;" + + "-fx-background-radius: 0 0 12 12;" + + "-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.08), 8, 0, 0, 3);" + ); + VBox.setVgrow(card, Priority.ALWAYS); // Title - Label title = new Label("Sync Service Client"); - title.setStyle("-fx-font-size: 24; -fx-font-weight: bold; -fx-text-fill: #1976d2;"); + Label title = new Label("Sync Manager"); + title.setStyle("-fx-font-size: 26; -fx-font-weight: bold; -fx-text-fill: #1565C0;"); - Label subtitle = new Label("Dolibarr ↔ PrestaShop Integration"); - subtitle.setStyle("-fx-font-size: 12; -fx-text-fill: #666666;"); + Label subtitle = new Label("Dolibarr · PrestaShop"); + subtitle.setStyle("-fx-font-size: 13; -fx-text-fill: #757575;"); - // URL input - Label urlLabel = new Label("Base URL:"); - urlLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 11;"); - TextField urlField = new TextField(); - urlField.setText("https://proyectointermodular-production-a9c3.up.railway.app"); + VBox titleBox = new VBox(4, title, subtitle); + titleBox.setAlignment(Pos.CENTER); + + Separator sep = new Separator(); + sep.setPadding(new Insets(4, 0, 4, 0)); + + // URL field + Label urlLabel = new Label("URL del servidor"); + urlLabel.setStyle("-fx-font-size: 11; -fx-font-weight: bold; -fx-text-fill: #546E7A;"); + TextField urlField = new TextField(sessionManager.getBaseUrl()); + urlField.setPromptText("https://tu-servidor.up.railway.app"); urlField.setStyle("-fx-font-size: 12; -fx-padding: 8;"); - // Username input - Label usernameLabel = new Label("Username:"); - usernameLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 11;"); - TextField usernameField = new TextField(); - usernameField.setPromptText("admin"); - usernameField.setText("admin"); - usernameField.setStyle("-fx-font-size: 12; -fx-padding: 8;"); + // Username + Label userLabel = new Label("Usuario"); + userLabel.setStyle("-fx-font-size: 11; -fx-font-weight: bold; -fx-text-fill: #546E7A;"); + TextField userField = new TextField( + sessionManager.getUsername() != null ? sessionManager.getUsername() : ""); + userField.setPromptText("admin"); + userField.setStyle("-fx-font-size: 12; -fx-padding: 8;"); - // Password input - Label passwordLabel = new Label("Password:"); - passwordLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 11;"); - PasswordField passwordField = new PasswordField(); - passwordField.setPromptText("••••••••"); - passwordField.setText("admin123"); - passwordField.setStyle("-fx-font-size: 12; -fx-padding: 8;"); - - // Login button - Button loginButton = new Button("Login"); - loginButton.setStyle( - "-fx-font-size: 12; " + - "-fx-padding: 10; " + - "-fx-background-color: #1976d2; " + - "-fx-text-fill: white; " + - "-fx-font-weight: bold; " + - "-fx-cursor: hand;" - ); - loginButton.setPrefWidth(Double.MAX_VALUE); - - loginButton.setOnAction(e -> handleLogin( - urlField.getText(), - usernameField.getText(), - passwordField.getText() - )); + // Password + Label passLabel = new Label("Contraseña"); + passLabel.setStyle("-fx-font-size: 11; -fx-font-weight: bold; -fx-text-fill: #546E7A;"); + PasswordField passField = new PasswordField(); + passField.setText(sessionManager.getPassword() != null ? sessionManager.getPassword() : ""); + passField.setPromptText("••••••••"); + passField.setStyle("-fx-font-size: 12; -fx-padding: 8;"); // Dev mode toggle - CheckBox devModeCheckBox = new CheckBox("Use local dev server (http://localhost:8080)"); - devModeCheckBox.setStyle("-fx-font-size: 11;"); - devModeCheckBox.selectedProperty().addListener((obs, old, newVal) -> { - if (newVal) { - urlField.setText("http://localhost:8080"); - } else { - urlField.setText("https://proyectointermodular-production-a9c3.up.railway.app"); - } - }); + CheckBox devCheckBox = new CheckBox("Usar servidor local (localhost:8080)"); + devCheckBox.setStyle("-fx-font-size: 11; -fx-text-fill: #546E7A;"); + devCheckBox.selectedProperty().addListener((obs, old, val) -> + urlField.setText(val ? DEV_URL : PROD_URL)); - // Separator - Separator separator = new Separator(); + // Login button + Button loginButton = new Button("Conectar"); + loginButton.getStyleClass().add("btn-primary"); + loginButton.setMaxWidth(Double.MAX_VALUE); + loginButton.setPrefHeight(42); + loginButton.setStyle(loginButton.getStyle() + + "-fx-font-size: 13; -fx-font-weight: bold; -fx-cursor: hand;"); + loginButton.setOnAction(e -> handleLogin(urlField, userField, passField)); - // Info label - Label infoLabel = new Label("Tip: Default credentials are admin / admin123"); - infoLabel.setStyle("-fx-font-size: 10; -fx-text-fill: #999999; -fx-font-style: italic;"); + // Allow Enter key in password field + passField.setOnAction(e -> handleLogin(urlField, userField, passField)); - form.getChildren().addAll( - title, - subtitle, - new Separator(), - urlLabel, - urlField, - usernameLabel, - usernameField, - passwordLabel, - passwordField, - separator, - devModeCheckBox, - new Region(), // spacer - loginButton, - infoLabel + card.getChildren().addAll( + titleBox, sep, + urlLabel, urlField, + userLabel, userField, + passLabel, passField, + devCheckBox, + new Region(), + loginButton ); + form.getChildren().addAll(topBar, card); return form; } - private void handleLogin(String baseUrl, String username, String password) { - if (baseUrl.isEmpty() || username.isEmpty() || password.isEmpty()) { - AlertUtil.showError("Validation Error", "All fields are required"); + private void handleLogin(TextField urlField, TextField userField, PasswordField passField) { + String url = urlField.getText().trim(); + String user = userField.getText().trim(); + String pass = passField.getText(); + + if (url.isEmpty() || user.isEmpty() || pass.isEmpty()) { + AlertUtil.showError("Validación", "Todos los campos son obligatorios."); return; } - // Ejecuta en background para no freezear UI - Thread loginThread = new Thread(() -> { + Thread t = new Thread(() -> { try { - boolean success = sessionManager.authenticate(baseUrl, username, password); + boolean success = sessionManager.authenticate(url, user, pass); Platform.runLater(() -> { if (success) { - AlertUtil.showInfo("Success", "Authentication successful!"); loginWindow.close(); - openMainStage(); + new MainStage(sessionManager, primaryStage).show(); } else { - AlertUtil.showError("Login Failed", - "Could not authenticate. Check credentials and URL."); + AlertUtil.showError("Autenticación fallida", + "No se pudo conectar. Verifica URL y credenciales."); } }); } catch (Exception ex) { - Platform.runLater(() -> { - AlertUtil.showError("Connection Error", ex.getMessage()); - }); + log.error("Login error: {}", ex.getMessage()); + Platform.runLater(() -> AlertUtil.showError("Error de conexión", ex.getMessage())); } }); - loginThread.setDaemon(true); - loginThread.start(); + t.setDaemon(true); + t.start(); } - - private void openMainStage() { - MainStage mainStage = new MainStage(sessionManager, primaryStage); - mainStage.show(); - } -} \ No newline at end of file +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/stages/MainStage.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/stages/MainStage.java index 4897e33..47487ad 100644 --- a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/stages/MainStage.java +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/stages/MainStage.java @@ -6,27 +6,18 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import javafx.stage.Stage; import lombok.extern.slf4j.Slf4j; /** - * Pantalla principal con navegación por tabs y dashboard. + * Ventana principal con navegación por tabs: Panel, Productos, Pedidos, Logs, Ajustes. */ @Slf4j public class MainStage { private final SessionManager sessionManager; private final Stage primaryStage; - private Stage mainWindow; - - private DashboardController dashboardController; - private ProductsController productsController; - private OrdersController ordersController; - private LogsController logsController; public MainStage(SessionManager sessionManager, Stage primaryStage) { this.sessionManager = sessionManager; @@ -34,12 +25,14 @@ public class MainStage { } public void show() { - mainWindow = new Stage(); - mainWindow.setTitle("TFG Sync Service — Dashboard"); + Stage mainWindow = new Stage(); + mainWindow.setTitle("Sync Manager"); mainWindow.setWidth(1200); - mainWindow.setHeight(700); + mainWindow.setHeight(720); + mainWindow.setMinWidth(900); + mainWindow.setMinHeight(600); - BorderPane root = createMainLayout(); + BorderPane root = createMainLayout(mainWindow); Scene scene = new Scene(root); scene.getStylesheets().add(getClass().getResource("/styles.css").toExternalForm()); @@ -48,92 +41,83 @@ public class MainStage { mainWindow.show(); } - private BorderPane createMainLayout() { + private BorderPane createMainLayout(Stage window) { BorderPane root = new BorderPane(); - - // Top: Header con usuario y logout - root.setTop(createHeader()); - - // Center: Tabs + root.setTop(createHeader(window)); root.setCenter(createTabPane()); - return root; } - private VBox createHeader() { - VBox header = new VBox(); - header.setPadding(new Insets(15)); - header.setStyle("-fx-background-color: #1976d2; -fx-border-color: #1565c0; -fx-border-width: 0 0 2 0;"); + private HBox createHeader(Stage window) { + HBox header = new HBox(12); + header.setPadding(new Insets(12, 20, 12, 20)); + header.setAlignment(Pos.CENTER_LEFT); + header.setStyle("-fx-background-color: #1565C0;"); - HBox topBar = new HBox(10); - topBar.setAlignment(Pos.CENTER_LEFT); + Label appName = new Label("Sync Manager"); + appName.setStyle("-fx-font-size: 17; -fx-font-weight: bold; -fx-text-fill: white;"); - Label title = new Label("Sync Service Control Panel"); - title.setStyle("-fx-font-size: 18; -fx-font-weight: bold; -fx-text-fill: white;"); + Label sep = new Label("·"); + sep.setStyle("-fx-text-fill: rgba(255,255,255,0.5); -fx-font-size: 14;"); + + Label connectedLabel = new Label("✓ " + sessionManager.getBaseUrl()); + connectedLabel.setStyle("-fx-font-size: 11; -fx-text-fill: rgba(255,255,255,0.75);"); Region spacer = new Region(); - HBox.setHgrow(spacer, javafx.scene.layout.Priority.ALWAYS); + HBox.setHgrow(spacer, Priority.ALWAYS); - Label userLabel = new Label("User: " + sessionManager.getUsername()); - userLabel.setStyle("-fx-font-size: 12; -fx-text-fill: white;"); + Label userChip = new Label(sessionManager.getUsername()); + userChip.setStyle( + "-fx-font-size: 11; -fx-text-fill: white;" + + "-fx-background-color: rgba(255,255,255,0.15);" + + "-fx-background-radius: 12; -fx-padding: 4 10;" + ); - Button logoutButton = new Button("Logout"); - logoutButton.setStyle("-fx-font-size: 11; -fx-padding: 5;"); - logoutButton.setOnAction(e -> handleLogout()); - - topBar.getChildren().addAll(title, spacer, userLabel, logoutButton); - - HBox statusBar = new HBox(20); - statusBar.setPadding(new Insets(10, 15, 10, 15)); - statusBar.setStyle("-fx-background-color: #f5f5f5;"); - - Label statusLabel = new Label("✓ Connected to: " + sessionManager.getBaseUrl()); - statusLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #2e7d32;"); - - statusBar.getChildren().add(statusLabel); - - header.getChildren().addAll(topBar, statusBar); + Button logoutBtn = new Button("Salir"); + logoutBtn.setStyle( + "-fx-background-color: rgba(255,255,255,0.15); -fx-text-fill: white;" + + "-fx-background-radius: 8; -fx-padding: 5 12; -fx-cursor: hand; -fx-font-size: 11;" + ); + logoutBtn.setOnAction(e -> handleLogout(window)); + header.getChildren().addAll(appName, sep, connectedLabel, spacer, userChip, logoutBtn); return header; } private TabPane createTabPane() { - TabPane tabPane = new TabPane(); + DashboardController dashboardController = new DashboardController(sessionManager); + ProductsController productsController = new ProductsController(sessionManager, dashboardController); + OrdersController ordersController = new OrdersController(sessionManager, dashboardController); + LogsController logsController = new LogsController(sessionManager); + SettingsController settingsController = new SettingsController(sessionManager); + + Tab dashboardTab = tab("📊 Panel", dashboardController.createView()); + Tab productsTab = tab("📦 Productos", productsController.createView()); + Tab ordersTab = tab("🛒 Pedidos", ordersController.createView()); + Tab logsTab = tab("📋 Historial", logsController.createView()); + Tab settingsTab = tab("⚙ Ajustes", settingsController.createView()); + + TabPane tabPane = new TabPane(dashboardTab, productsTab, ordersTab, logsTab, settingsTab); tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); tabPane.setStyle("-fx-font-size: 12;"); - - // Dashboard tab - dashboardController = new DashboardController(sessionManager); - Tab dashboardTab = new Tab("📊 Dashboard", dashboardController.createView()); - dashboardTab.setStyle("-fx-padding: 10;"); - - // Products tab - productsController = new ProductsController(sessionManager, dashboardController); - Tab productsTab = new Tab("📦 Products", productsController.createView()); - - // Orders tab - ordersController = new OrdersController(sessionManager, dashboardController); - Tab ordersTab = new Tab("🛒 Orders", ordersController.createView()); - - // Logs tab - logsController = new LogsController(sessionManager); - Tab logsTab = new Tab("📋 Logs", logsController.createView()); - - tabPane.getTabs().addAll(dashboardTab, productsTab, ordersTab, logsTab); - return tabPane; } - private void handleLogout() { - Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.setTitle("Logout"); - alert.setHeaderText("Are you sure?"); - alert.setContentText("You will be logged out and returned to the login screen."); - - if (alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) { - sessionManager.logout(); - mainWindow.close(); - new LoginStage(sessionManager, primaryStage).show(); - } + private Tab tab(String label, javafx.scene.Node content) { + Tab t = new Tab(label, content); + t.setStyle("-fx-padding: 8 12;"); + return t; } -} \ No newline at end of file + + private void handleLogout(Stage window) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("Cerrar sesión"); + alert.setHeaderText("¿Cerrar sesión?"); + alert.setContentText("Volverás a la pantalla de inicio de sesión."); + if (alert.showAndWait().orElse(ButtonType.CANCEL) != ButtonType.OK) return; + + sessionManager.logout(); + window.close(); + new LoginStage(sessionManager, primaryStage).show(); + } +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/DateUtils.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/DateUtils.java new file mode 100644 index 0000000..83c3cee --- /dev/null +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/DateUtils.java @@ -0,0 +1,105 @@ +package com.teterialosjuanjos.tfg.javafxclient.util; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +/** Utilidades de formateo de fechas y etiquetas de dominio. */ +public class DateUtils { + + private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); + + public static String formatRelative(String isoInstant) { + if (isoInstant == null || isoInstant.isBlank()) return "—"; + try { + long seconds = Instant.now().getEpochSecond() - Instant.parse(isoInstant).getEpochSecond(); + if (seconds < 60) return "hace " + seconds + "s"; + if (seconds < 3600) return "hace " + (seconds / 60) + " min"; + if (seconds < 86400) return "hace " + (seconds / 3600) + " h"; + return "hace " + (seconds / 86400) + " días"; + } catch (Exception e) { + return isoInstant.length() >= 19 ? isoInstant.substring(0, 19) : isoInstant; + } + } + + public static String formatDateTime(String isoInstant) { + if (isoInstant == null || isoInstant.isBlank()) return "—"; + try { + LocalDateTime ldt = LocalDateTime.ofInstant(Instant.parse(isoInstant), ZoneId.systemDefault()); + return ldt.format(DT_FMT); + } catch (Exception e) { + return isoInstant.length() >= 19 ? isoInstant.substring(0, 19) : isoInstant; + } + } + + public static String formatDuration(String start, String end) { + if (start == null || end == null) return "—"; + try { + long seconds = Instant.parse(end).getEpochSecond() - Instant.parse(start).getEpochSecond(); + if (seconds < 60) return seconds + "s"; + return (seconds / 60) + "m " + (seconds % 60) + "s"; + } catch (Exception e) { + return "—"; + } + } + + public static String syncTypeLabel(String syncType) { + if (syncType == null) return "—"; + return switch (syncType) { + case "PRODUCT_PUSH" -> "Productos"; + case "STOCK_PUSH" -> "Stock"; + case "ORDER_PULL" -> "Pedidos"; + default -> syncType; + }; + } + + public static String syncTypeDirection(String syncType) { + if (syncType == null) return ""; + return switch (syncType) { + case "PRODUCT_PUSH", "STOCK_PUSH" -> "Dolibarr → PrestaShop"; + case "ORDER_PULL" -> "PrestaShop → Dolibarr"; + default -> ""; + }; + } + + public static String productStatusLabel(String status) { + if (status == null) return "—"; + return switch (status) { + case "SYNCED" -> "Sincronizado"; + case "PENDING" -> "Pendiente"; + case "ERROR" -> "Error"; + default -> status; + }; + } + + public static String orderStatusLabel(String status) { + if (status == null) return "—"; + return switch (status) { + case "IMPORTED" -> "Importado"; + case "INVOICED" -> "Facturado"; + case "ERROR" -> "Error"; + default -> status; + }; + } + + public static String productStatusCssClass(String status) { + if (status == null) return ""; + return switch (status) { + case "SYNCED" -> "chip-synced"; + case "PENDING" -> "chip-pending"; + case "ERROR" -> "chip-error-status"; + default -> ""; + }; + } + + public static String orderStatusCssClass(String status) { + if (status == null) return ""; + return switch (status) { + case "IMPORTED" -> "chip-imported"; + case "INVOICED" -> "chip-invoiced"; + case "ERROR" -> "chip-error-status"; + default -> ""; + }; + } +} diff --git a/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/SettingsStore.java b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/SettingsStore.java new file mode 100644 index 0000000..51475ac --- /dev/null +++ b/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/SettingsStore.java @@ -0,0 +1,37 @@ +package com.teterialosjuanjos.tfg.javafxclient.util; + +import com.teterialosjuanjos.tfg.javafxclient.model.AppSettings; + +import java.util.prefs.Preferences; + +/** + * Persistencia de ajustes de conexión usando Java Preferences API. + * En Windows: HKCU\Software\JavaSoft\Prefs + * En Linux/Mac: ~/.java/.userPrefs + */ +public class SettingsStore { + + private static final Preferences prefs = Preferences.userNodeForPackage(SettingsStore.class); + + private static final String KEY_URL = "base_url"; + private static final String KEY_USERNAME = "username"; + private static final String KEY_PASSWORD = "password"; + + private static final String DEFAULT_URL = "https://proyectointermodular-production-a9c3.up.railway.app"; + private static final String DEFAULT_USER = "admin"; + private static final String DEFAULT_PASS = "admin123"; + + public static AppSettings load() { + return new AppSettings( + prefs.get(KEY_URL, DEFAULT_URL), + prefs.get(KEY_USERNAME, DEFAULT_USER), + prefs.get(KEY_PASSWORD, DEFAULT_PASS) + ); + } + + public static void save(AppSettings settings) { + prefs.put(KEY_URL, settings.getBaseUrl()); + prefs.put(KEY_USERNAME, settings.getUsername()); + prefs.put(KEY_PASSWORD, settings.getPassword()); + } +} diff --git a/javafx-client/src/main/resources/logback.xml b/javafx-client/src/main/resources/logback.xml index 78a646b..2b70989 100644 --- a/javafx-client/src/main/resources/logback.xml +++ b/javafx-client/src/main/resources/logback.xml @@ -10,5 +10,5 @@ - - \ No newline at end of file + + diff --git a/javafx-client/src/main/resources/styles.css b/javafx-client/src/main/resources/styles.css index 75a7960..8e5cd0e 100644 --- a/javafx-client/src/main/resources/styles.css +++ b/javafx-client/src/main/resources/styles.css @@ -1,93 +1,261 @@ -/* Material Design Inspired CSS for TFG Sync Service Client */ +/* TFG Sync Service Client — Material 3 (Android palette) */ * { - -fx-font-family: "Segoe UI", "Ubuntu", sans-serif; + -fx-font-family: "Segoe UI", "Roboto", sans-serif; } .root { - -fx-background-color: #fafafa; - -fx-font-size: 12; + -fx-background-color: #FAFAFA; + -fx-font-size: 12px; } -/* Buttons */ +/* === BUTTONS === */ .button { - -fx-padding: 8; - -fx-font-size: 12; - -fx-text-fill: #333; - -fx-background-color: #e0e0e0; - -fx-background-radius: 4; + -fx-padding: 8 16 8 16; + -fx-font-size: 12px; + -fx-background-radius: 8; + -fx-background-color: #E0E0E0; + -fx-text-fill: #212121; + -fx-cursor: hand; + -fx-border-width: 0; } +.button:hover { -fx-background-color: #BDBDBD; } +.button:pressed { -fx-background-color: #9E9E9E; } -.button:hover { - -fx-background-color: #d0d0d0; -} - -/* Text Fields */ -.text-field, -.password-field { - -fx-padding: 8; - -fx-border-color: #bdbdbd; - -fx-border-width: 0 0 1 0; - -fx-font-size: 12; -} - -/* Labels */ -.label { - -fx-text-fill: #333; -} - -/* Tables */ -.table-view { - -fx-background-color: white; - -fx-border-color: #e0e0e0; - -fx-font-size: 11; -} - -.table-view .column-header { - -fx-background-color: #f5f5f5; +.btn-primary { + -fx-background-color: #1565C0; + -fx-text-fill: white; -fx-font-weight: bold; } +.btn-primary:hover { -fx-background-color: #1976D2; } -.table-row-cell { - -fx-font-size: 11; - -fx-background-color: white; +.btn-success { + -fx-background-color: #2E7D32; + -fx-text-fill: white; +} +.btn-success:hover { -fx-background-color: #388E3C; } + +.btn-warning { + -fx-background-color: #E65100; + -fx-text-fill: white; +} +.btn-warning:hover { -fx-background-color: #EF6C00; } + +.btn-secondary { + -fx-background-color: #546E7A; + -fx-text-fill: white; +} +.btn-secondary:hover { -fx-background-color: #607D8B; } + +.btn-outline { + -fx-background-color: transparent; + -fx-border-color: #1565C0; + -fx-border-width: 1; + -fx-border-radius: 8; + -fx-background-radius: 8; + -fx-text-fill: #1565C0; +} +.btn-outline:hover { -fx-background-color: #E3F2FD; } + +/* === FILTER CHIPS (ToggleButton) === */ +.filter-chip { + -fx-background-color: #ECEFF1; + -fx-text-fill: #546E7A; + -fx-background-radius: 20; + -fx-border-radius: 20; + -fx-padding: 6 16 6 16; + -fx-font-size: 11px; + -fx-cursor: hand; + -fx-border-width: 1; + -fx-border-color: #CFD8DC; +} +.filter-chip:selected { + -fx-background-color: #1565C0; + -fx-text-fill: white; + -fx-border-color: #1565C0; +} +.filter-chip:hover { + -fx-background-color: #CFD8DC; +} +.filter-chip:selected:hover { + -fx-background-color: #1976D2; } -/* ComboBox */ -.combo-box, -.combo-box-base { +/* === STATUS CHIPS (Label) === */ +.chip-synced { + -fx-background-color: #E8F5E9; + -fx-text-fill: #2E7D32; + -fx-background-radius: 16; + -fx-padding: 2 10 2 10; + -fx-font-weight: bold; + -fx-font-size: 10px; +} +.chip-pending { + -fx-background-color: #FFF3E0; + -fx-text-fill: #E65100; + -fx-background-radius: 16; + -fx-padding: 2 10 2 10; + -fx-font-weight: bold; + -fx-font-size: 10px; +} +.chip-error-status { + -fx-background-color: #FFEBEE; + -fx-text-fill: #C62828; + -fx-background-radius: 16; + -fx-padding: 2 10 2 10; + -fx-font-weight: bold; + -fx-font-size: 10px; +} +.chip-imported { + -fx-background-color: #ECEFF1; + -fx-text-fill: #546E7A; + -fx-background-radius: 16; + -fx-padding: 2 10 2 10; + -fx-font-weight: bold; + -fx-font-size: 10px; +} +.chip-invoiced { + -fx-background-color: #E3F2FD; + -fx-text-fill: #1565C0; + -fx-background-radius: 16; + -fx-padding: 2 10 2 10; + -fx-font-weight: bold; + -fx-font-size: 10px; +} + +/* === BANNERS === */ +.banner-error { + -fx-background-color: #FFEBEE; + -fx-border-color: #EF9A9A; + -fx-border-width: 0 0 0 4; + -fx-background-radius: 4; + -fx-padding: 12; +} +.banner-warning { + -fx-background-color: #FFF8E1; + -fx-border-color: #FFD54F; + -fx-border-width: 0 0 0 4; + -fx-background-radius: 4; + -fx-padding: 12; +} +.banner-info { + -fx-background-color: #E3F2FD; + -fx-border-color: #90CAF9; + -fx-border-width: 0 0 0 4; + -fx-background-radius: 4; + -fx-padding: 12; +} +.banner-success { + -fx-background-color: #E8F5E9; + -fx-border-color: #A5D6A7; + -fx-border-width: 0 0 0 4; + -fx-background-radius: 4; + -fx-padding: 12; +} + +/* === TEXT FIELDS === */ +.text-field, .password-field { + -fx-padding: 10; -fx-background-color: white; - -fx-border-color: #bdbdbd; + -fx-background-radius: 8; + -fx-border-color: #BDBDBD; + -fx-border-width: 1; + -fx-border-radius: 8; + -fx-font-size: 12px; +} +.text-field:focused, .password-field:focused { + -fx-border-color: #1565C0; + -fx-border-width: 2; + -fx-background-insets: 0; +} + +/* === LABELS === */ +.label { -fx-text-fill: #212121; } + +/* === TABLES === */ +.table-view { + -fx-background-color: white; + -fx-border-color: #E0E0E0; + -fx-border-width: 1; + -fx-border-radius: 8; + -fx-background-radius: 8; + -fx-font-size: 11px; +} +.table-view .column-header { + -fx-background-color: #F5F5F5; + -fx-font-weight: bold; + -fx-padding: 10 8 10 8; + -fx-text-fill: #546E7A; + -fx-font-size: 11px; + -fx-border-color: transparent; +} +.table-view .column-header-background { + -fx-background-color: #F5F5F5; + -fx-border-color: #E0E0E0; + -fx-border-width: 0 0 1 0; +} +.table-row-cell { + -fx-background-color: white; + -fx-padding: 4 0 4 0; + -fx-border-color: transparent transparent #F5F5F5 transparent; + -fx-border-width: 0 0 1 0; +} +.table-row-cell:odd { -fx-background-color: #FAFAFA; } +.table-row-cell:hover { -fx-background-color: #E3F2FD; } +.table-row-cell:selected { -fx-background-color: #BBDEFB; } + +/* === TEXT AREA === */ +.text-area { + -fx-background-color: #F5F5F5; + -fx-text-fill: #333; + -fx-font-size: 11px; + -fx-background-radius: 8; + -fx-border-radius: 8; + -fx-border-color: #E0E0E0; + -fx-border-width: 1; +} +.text-area .content { + -fx-background-color: #F5F5F5; + -fx-background-radius: 8; +} + +/* === COMBO BOX === */ +.combo-box { + -fx-background-color: white; + -fx-border-color: #BDBDBD; + -fx-border-radius: 8; + -fx-background-radius: 8; -fx-padding: 5; } -/* TextArea */ -.text-area { - -fx-background-color: #f5f5f5; - -fx-text-fill: #333; - -fx-font-size: 10; +/* === TAB PANE === */ +.tab-pane > .tab-header-area > .tab-header-background { + -fx-background-color: #F5F5F5; + -fx-border-color: #E0E0E0; + -fx-border-width: 0 0 1 0; } - -/* Separator */ -.separator { - -fx-text-fill: #e0e0e0; -} - -/* TabPane */ -.tab-pane { - -fx-padding: 0; -} - .tab { - -fx-padding: 10; + -fx-background-color: transparent; + -fx-padding: 10 20 10 20; + -fx-font-size: 12px; + -fx-cursor: hand; } - -.tab-header-background { - -fx-background-color: #f5f5f5; +.tab:selected { + -fx-background-color: white; + -fx-border-color: #1565C0; + -fx-border-width: 0 0 3 0; } +.tab .tab-label { -fx-text-fill: #757575; } +.tab:selected .tab-label { -fx-text-fill: #1565C0; -fx-font-weight: bold; } +.tab-pane { -fx-tab-min-height: 44px; } -/* CheckBox */ -.check-box { - -fx-font-size: 11; - -fx-text-fill: #333; -} \ No newline at end of file +/* === SCROLL PANE === */ +.scroll-pane { -fx-background-color: transparent; -fx-border-color: transparent; } +.scroll-pane .viewport { -fx-background-color: transparent; } +.scroll-pane > .scroll-bar { -fx-background-color: transparent; } + +/* === SEPARATOR === */ +.separator .line { -fx-border-color: #E0E0E0; -fx-border-width: 1; } + +/* === CHECK BOX === */ +.check-box { -fx-font-size: 11px; -fx-text-fill: #212121; } diff --git a/javafx-client/target/classes/applications.properties b/javafx-client/target/classes/applications.properties deleted file mode 100644 index e69de29..0000000 diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/JavaFxClientApplication.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/JavaFxClientApplication.class deleted file mode 100644 index 179aa6fa2746aa964573bde16959f8626a0d453a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1442 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 diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/dto/MappingDto.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/dto/MappingDto.class deleted file mode 100644 index 1a16dee040a4024536a00bfc65498e9f505ac9dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 353 zcmbtQOHRWu5Pg$R1EG+L8?XZw_y7eY1Y#BFqNsZ(b%P_vt`Y}vEmlY@H~@!2j4LD# zz>M@}-W$zF^Y#7l3E&2^91-FS$rLFjgjp+Jq%hJpVo|lK_Jql$w%T74;?uKLhCRZ( zcAfCbt6odvhIW*;b%XGmMuhgwYi+c$UUY7)j97ao9^`hbZFA=xA-QFlP?XxL$FZwa z|0FBJ%5mvxX;zY_GXE75|EveXRr!ydb3(p!V_&O#9TpXT=Ujw?ae|EB5RBOQ5sdrH h#Et{$`8(*1g$OxgC<8GL7^k~D<#@=9aKsp)_yMAoSZDwM diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/dto/OrderDto.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/dto/OrderDto.class deleted file mode 100644 index a572b01d1a433fae89fc949644bbb82da0dfacbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 347 zcmbVIOHRWu5Pg$R1EHlAH(&=W@ER2%ArPyGEZF1T zFeAO0_eS&4eE)oY0l3AYM1r(HHbssJVbRK0DXer`v8r3GB4KiEoQXGt^z3|H;DB(Y zd?%ugIv8nv-wx8Xz87(`6=VD6O~ypnb;c_#B;F^eyzCoHRfZ(I_}CqD$GAI!LB_IJwVcq&d&@B>C8c76flAv3Y( fKz{K7dS@X)$vBpQ6i1BHeV%iiF(Vu^CYbL4yFyj+ diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/dto/ProductDto.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/model/dto/ProductDto.class deleted file mode 100644 index d6104840680adab3ba9c445dcb8e4a01d63a8b32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 353 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 diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LoginController.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LoginController.class deleted file mode 100644 index 5a2fdc836ca706c8929e3e0547c46fb2d864299a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 378 zcmb_Y%Sr?>5UifZj*hFlh(F*>J(v$r1Xgn=wTgtvxp5|55b~qrWrZ!m zmGWy5b=1K~>xX`nuJ?n8t4^fns~2UBc2SH*DDPtM*6J{b#&^aoeg#6Y@Q<1>YmC#k z$WSw;GLU13ak|M%zIT}s_82qFz5xblV|f4o diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController$1.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController$1.class deleted file mode 100644 index 141fd6d484709d19f5af13db82d70ccd081cb5e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1506 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 Z8cO*vU9Cw1a0ABipSD@ssx?sQ{{vssQa1nq diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController$1.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController$1.class deleted file mode 100644 index 19b2afb48e0324be7324fff0b1bb45d34844f2da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1845 zcmb_d&2Jk;6#q?Z#~XL!xSKlRBMolqLcGS=I&G5>hfwOIO_1Y8aTST&HtTV+VZ9r6 z$0X$h5+CAALcpa+T&V=jflDfJK-`e{54dsU%#8zxH|s4#CE=8nX5YNG^S*xXz1g4t z`u+z1FJn1{9>kJ}t4N@iVRXlQ!))yvfyG^JSe`3<&oSrz=!(T5a6s*YRl?{1bMSl`$6$8jHykdE6LvX=; z+jP8Or(?Q1USNpUmeCEva%}Djqhmjc#NmEmtomNFV~L>JjgDmbGD9-p;tt;z4B4a3 z8^X8Utx92CfyMwC!8sLK>0Tj?hPdnS6o&Cc5>Kjl3Qsd+{tdmWO-HxQV7uxyd6@UI zg7bt#QhJuR}=jvj8VYz-w!6k;o;@aBnH3iQx zOq5#tB|!<4TDIfN<}Ob1>E@NmN>0jKlxk^@+gsaWHn-_H&5DA;F*>bl)2;IK$vVYj zbgZe#naP=xbR=LUS>lPusu}@HRb^NiuboyYsZtE_F4O}x+vW96dz1TN2Qp|i&oZ5L zlNyFPG7`qcwoRDVPA!Ifxk9y+R`ZvsOHa)%S-ZMGX*=Y2_`kzAA;*VdV9~W`sA>4RDJM~7SURmXQL%`d41<=> z>C$-=M1;)Pc;SEWShx8erJ>Y$AeOvX}PCkZ)FgdqeLRI$O_~kL7xuv z;>bfJN4`X|_!G|R>=0*k?d%~2@8fg5NL;brGZil#V(43Gwni)V*@Iu-Bs$hJlHDU( zj{W}2Z$z(`w2?!M-a|e%6~B+sk~S8y*|=l}N?JZ-dlOUfZ2TKipr;$GOb8gEb&1x5 zu00zDpy}Fpxare1oi;M_SeUs^w<+)rA@DBJcn^bkpVU9V1$>Av@DaYk$M_nb-~m1j z(aD8?9{hvO0cJu_2>{HZLhk3t-Z`Yw{eQwpVwk1VXU;3iA4p^t zz4#-BDck3duZ9{{CKdUUHRz)ZGel`6@FHz`Fprl)dYyiTpz7Sf0&azp8(1QmA|onM Gdj1A)9Mn+& diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController.class deleted file mode 100644 index 8c1546495fb10312b28c8853fb5984930f138a89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15178 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 diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/stages/MainStage.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/ui/stages/MainStage.class deleted file mode 100644 index 6cc3c3b167830f0595c2856057b3b60e9d095e28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8330 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 diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/util/AlertUtil.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/util/AlertUtil.class deleted file mode 100644 index eca27ecb74aa0f7cbf10f5f083d2b9e9f95a1575..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1299 zcmbVLYflqF6g^W~mX@Uy*bvYrrA`oPbmVPLS zCjJ0_l=054;0MJt>?Sj__ue_@+_N)3et!K9po|wO#4unWZX$s}hD?Wl;Fil}+o~US zgsm6`UpmrJuNVgMg#!aa3|U=td~OA{kixP(seI41%C7KL5-AKDFingg&9HoZ<#Jfx z{B$N5hAXw*dSkELtki1^E7g8>l~OQ}(Xz)F3iyV6yAT;z*D`6Z}y3Jzn)} z?jCT$qwn9rxH@qHhOO$2*h<=1YP%wjJ%-hP7#o=o9!`y%V_@#sv_a#G8tJ(2=&u2*OdtKiayN+%?edW-Gc8|fVNGbenmj{6$1Cm!q z6iZm4i=v-6NCO6G%+r@eGov}Nb^-R87BLiP)M+4zH5$#R0P84*vHRGdh~DgDfLkn?0kTv2kU9Sw%n8YZuv|7Yi$d!xmNY(?Y zBV;_J6`ez>e1ylOE{P}C@#p&Gp9t|Xm?G3^;-4Y@*}nNVuj9YlFaI3z&r{b6#J@=V bIU1Mxa{9B#`5B>muRB^Tl z-kpIr^WHFD-`meG0C$*`NRSrDrpPfN%v$*>g_Uk27EP;FAWUwIGvStyo}R4=>=7=N z??li+_eNSjw4-#bA4FJhL~LKbDQmO~VhqNLx$ip}o;W3B_x@QEs@gdHFm{dZA7x`% zIj+5ub|rZr^ItIwFJ>TI)&E;}O(>Us?3JFIxTE^JG-BuX8TXlq9S8FB c570Xc2};IT22va_PIr0E@sJteh%rI+12zU#2mk;8 diff --git a/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/util/TaskExecutor.class b/javafx-client/target/classes/com/teterialosjuanjos/tfg/javafxclient/util/TaskExecutor.class deleted file mode 100644 index 8b73383d90d80c93c8ad63ccca018a59f1f5a97a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349 zcmbVIyG{c!5S+b74vs(qe?SKmxJ7`F5Q6Fi1yOpRae@!WmhNnn-=adI-~;$5#5$s& zW>=csnbkhDU*8{}0Io5uks+^8j8S4ln0E423M<`GJhYuwfiSu>&V(yMesnyquthji zekFnqx;N7Lp^MUWeh^`?6sdi&R@P`2L=47?nH*kj*ILEkdqQ#JpEP087^m;!s@45W zww9HH#w%&(l1DQC6^rm}2Euvs--Q>1`q9T;={u9Q?Ek&yG#!gWRQ!Tu#LiD(+-4>= f94JrTL2oQ%s2Nik$g#sX-sC06U1o$m#tf4mD1}#_ diff --git a/javafx-client/target/classes/fxml/dashboard.fxml b/javafx-client/target/classes/fxml/dashboard.fxml deleted file mode 100644 index e69de29..0000000 diff --git a/javafx-client/target/classes/fxml/login.fxml b/javafx-client/target/classes/fxml/login.fxml deleted file mode 100644 index e69de29..0000000 diff --git a/javafx-client/target/classes/fxml/logs.fxml b/javafx-client/target/classes/fxml/logs.fxml deleted file mode 100644 index e69de29..0000000 diff --git a/javafx-client/target/classes/fxml/orders.fxml b/javafx-client/target/classes/fxml/orders.fxml deleted file mode 100644 index e69de29..0000000 diff --git a/javafx-client/target/classes/fxml/products.fxml b/javafx-client/target/classes/fxml/products.fxml deleted file mode 100644 index e69de29..0000000 diff --git a/javafx-client/target/classes/logback.xml b/javafx-client/target/classes/logback.xml deleted file mode 100644 index 78a646b..0000000 --- a/javafx-client/target/classes/logback.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - %d{HH:mm:ss.SSS} [%-5level] %logger{36} - %msg%n - - - - - - - - - \ No newline at end of file diff --git a/javafx-client/target/classes/styles.css b/javafx-client/target/classes/styles.css deleted file mode 100644 index 75a7960..0000000 --- a/javafx-client/target/classes/styles.css +++ /dev/null @@ -1,93 +0,0 @@ -/* Material Design Inspired CSS for TFG Sync Service Client */ - -* { - -fx-font-family: "Segoe UI", "Ubuntu", sans-serif; -} - -.root { - -fx-background-color: #fafafa; - -fx-font-size: 12; -} - -/* Buttons */ -.button { - -fx-padding: 8; - -fx-font-size: 12; - -fx-text-fill: #333; - -fx-background-color: #e0e0e0; - -fx-background-radius: 4; -} - -.button:hover { - -fx-background-color: #d0d0d0; -} - -/* Text Fields */ -.text-field, -.password-field { - -fx-padding: 8; - -fx-border-color: #bdbdbd; - -fx-border-width: 0 0 1 0; - -fx-font-size: 12; -} - -/* Labels */ -.label { - -fx-text-fill: #333; -} - -/* Tables */ -.table-view { - -fx-background-color: white; - -fx-border-color: #e0e0e0; - -fx-font-size: 11; -} - -.table-view .column-header { - -fx-background-color: #f5f5f5; - -fx-font-weight: bold; -} - -.table-row-cell { - -fx-font-size: 11; - -fx-background-color: white; -} - -/* ComboBox */ -.combo-box, -.combo-box-base { - -fx-background-color: white; - -fx-border-color: #bdbdbd; - -fx-padding: 5; -} - -/* TextArea */ -.text-area { - -fx-background-color: #f5f5f5; - -fx-text-fill: #333; - -fx-font-size: 10; -} - -/* Separator */ -.separator { - -fx-text-fill: #e0e0e0; -} - -/* TabPane */ -.tab-pane { - -fx-padding: 0; -} - -.tab { - -fx-padding: 10; -} - -.tab-header-background { - -fx-background-color: #f5f5f5; -} - -/* CheckBox */ -.check-box { - -fx-font-size: 11; - -fx-text-fill: #333; -} \ No newline at end of file diff --git a/javafx-client/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/javafx-client/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst deleted file mode 100644 index d8a1738..0000000 --- a/javafx-client/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ /dev/null @@ -1,20 +0,0 @@ -com/teterialosjuanjos/tfg/javafxclient/ui/controllers/OrdersController.class -com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController$1.class -com/teterialosjuanjos/tfg/javafxclient/util/AlertUtil.class -com/teterialosjuanjos/tfg/javafxclient/ui/stages/MainStage.class -com/teterialosjuanjos/tfg/javafxclient/model/dto/OrderDto.class -com/teterialosjuanjos/tfg/javafxclient/model/SessionManager.class -com/teterialosjuanjos/tfg/javafxclient/util/TaskExecutor.class -com/teterialosjuanjos/tfg/javafxclient/JavaFxClientApplication.class -com/teterialosjuanjos/tfg/javafxclient/util/FormatUtil.class -com/teterialosjuanjos/tfg/javafxclient/ui/controllers/DashboardController.class -com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController.class -com/teterialosjuanjos/tfg/javafxclient/ui/stages/LoginStage.class -com/teterialosjuanjos/tfg/javafxclient/ui/controllers/OrdersController$1.class -com/teterialosjuanjos/tfg/javafxclient/model/dto/ProductDto.class -com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LoginController.class -com/teterialosjuanjos/tfg/javafxclient/api/SyncServiceClient.class -com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController.class -com/teterialosjuanjos/tfg/javafxclient/model/dto/MappingDto.class -com/teterialosjuanjos/tfg/javafxclient/model/dto/SyncDto.class -com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController$1.class diff --git a/javafx-client/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/javafx-client/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst deleted file mode 100644 index ff74091..0000000 --- a/javafx-client/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ /dev/null @@ -1,17 +0,0 @@ -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/ProductsController.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/DashboardController.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/JavaFxClientApplication.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/api/SyncServiceClient.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/stages/LoginStage.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/dto/OrderDto.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/FormatUtil.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/SessionManager.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/dto/SyncDto.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LoginController.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/OrdersController.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/dto/MappingDto.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/AlertUtil.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/util/TaskExecutor.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/model/dto/ProductDto.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/stages/MainStage.java -/home/mireyasp/Descargas/PROYECTO INTERMODULAR/ProyectoIntermodular/javafx-client/src/main/java/com/teterialosjuanjos/tfg/javafxclient/ui/controllers/LogsController.java