commit
f50f64dcdf
|
|
@ -0,0 +1,145 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.teterialosjuanjos.tfg</groupId>
|
||||
<artifactId>javafx-client</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>javafx-client</name>
|
||||
<description>TFG — JavaFX Desktop Client para Sync Service</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<javafx.version>21.0.2</javafx.version>
|
||||
<javafx.maven.plugin.version>0.0.8</javafx.maven.plugin.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- JavaFX - TODO los módulos necesarios -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-base</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-swing</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON parsing (Jackson) -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.18.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.5.6</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>5.10.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>21</source>
|
||||
<target>21</target>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- JavaFX Maven Plugin - ESENCIAL -->
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>${javafx.maven.plugin.version}</version>
|
||||
<configuration>
|
||||
<mainClass>com.teterialosjuanjos.tfg.javafxclient.JavaFxClientApplication</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- JAR ejecutable para producción -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<shadedClassifierName>fat</shadedClassifierName>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.teterialosjuanjos.tfg.javafxclient.JavaFxClientApplication</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.teterialosjuanjos.tfg.javafxclient;
|
||||
|
||||
import com.teterialosjuanjos.tfg.javafxclient.model.SessionManager;
|
||||
import com.teterialosjuanjos.tfg.javafxclient.ui.stages.LoginStage;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* JavaFX Desktop Client para TFG Integración Dolibarr-PrestaShop.
|
||||
*
|
||||
* <p>Punto de entrada de la aplicación. Inicia sesión y luego accede al
|
||||
* {@code sync-service} en Railway para ver/crear productos, pedidos, logs.</p>
|
||||
*/
|
||||
@Slf4j
|
||||
public class JavaFxClientApplication extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
log.info("Starting JavaFX Client Application");
|
||||
|
||||
// Inicializa SessionManager (almacena credenciales + cliente HTTP)
|
||||
SessionManager sessionManager = new SessionManager();
|
||||
|
||||
// Muestra LoginStage
|
||||
LoginStage loginStage = new LoginStage(sessionManager, primaryStage);
|
||||
loginStage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
package com.teterialosjuanjos.tfg.javafxclient.api;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Cliente HTTP tipado para el sync-service en Railway.
|
||||
*
|
||||
* <p>Maneja autenticación Basic Auth para todos los endpoints.</p>
|
||||
*/
|
||||
@Slf4j
|
||||
public class SyncServiceClient {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final String baseUrl;
|
||||
private final String authHeader;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public SyncServiceClient(String baseUrl, String username, String password) {
|
||||
this.baseUrl = baseUrl.replaceAll("/$", ""); // Remove trailing slash
|
||||
this.httpClient = HttpClient.newHttpClient();
|
||||
this.authHeader = buildBasicAuthHeader(username, password);
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.objectMapper.registerModule(new JavaTimeModule());
|
||||
}
|
||||
|
||||
// ── Health Check ────────────────────────────────────────────────────────
|
||||
|
||||
public Map<String, Object> checkHealth() throws IOException, InterruptedException {
|
||||
String url = baseUrl + "/actuator/health";
|
||||
return getJson(url, Map.class);
|
||||
}
|
||||
|
||||
// ── Logs ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Obtiene todos los logs de sincronización.
|
||||
*
|
||||
* @param type opcional, filtra por tipo (PRODUCT_PUSH, STOCK_PUSH, ORDER_PULL)
|
||||
*/
|
||||
public List<Map<String, Object>> getLogs(String type) throws IOException, InterruptedException {
|
||||
String url = baseUrl + "/api/logs";
|
||||
if (type != null && !type.isEmpty()) {
|
||||
url += "?type=" + type;
|
||||
}
|
||||
|
||||
HttpResponse<String> 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene un log específico por ID.
|
||||
*/
|
||||
public Map<String, Object> getLog(Long id) throws IOException, InterruptedException {
|
||||
String url = baseUrl + "/api/logs/" + id;
|
||||
return getJson(url, Map.class);
|
||||
}
|
||||
|
||||
// ── Mappings ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Obtiene todos los mapeos de productos.
|
||||
*
|
||||
* @param status opcional, filtra por status (PENDING, SYNCED, ERROR)
|
||||
*/
|
||||
public List<Map<String, Object>> getProductMappings(String status)
|
||||
throws IOException, InterruptedException {
|
||||
String url = baseUrl + "/api/mappings/products";
|
||||
if (status != null && !status.isEmpty()) {
|
||||
url += "?status=" + status;
|
||||
}
|
||||
|
||||
HttpResponse<String> 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene todos los mapeos de pedidos.
|
||||
*/
|
||||
public List<Map<String, Object>> getOrderMappings() throws IOException, InterruptedException {
|
||||
String url = baseUrl + "/api/mappings/orders";
|
||||
|
||||
HttpResponse<String> 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());
|
||||
}
|
||||
|
||||
// ── Sync Triggers ───────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Dispara sincronización de productos.
|
||||
*/
|
||||
public Map<String, Object> triggerProductSync() throws IOException, InterruptedException {
|
||||
String url = baseUrl + "/api/sync/products";
|
||||
return postJson(url, new HashMap<>(), Map.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispara sincronización de stock.
|
||||
*/
|
||||
public Map<String, Object> triggerStockSync() throws IOException, InterruptedException {
|
||||
String url = baseUrl + "/api/sync/stock";
|
||||
return postJson(url, new HashMap<>(), Map.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispara sincronización de pedidos.
|
||||
*/
|
||||
public Map<String, Object> triggerOrderSync() throws IOException, InterruptedException {
|
||||
String url = baseUrl + "/api/sync/orders";
|
||||
return postJson(url, new HashMap<>(), Map.class);
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
private <T> T getJson(String url, Class<T> responseType) throws IOException, InterruptedException {
|
||||
HttpResponse<String> response = sendRequest("GET", url, null);
|
||||
if (response.statusCode() == 200) {
|
||||
return objectMapper.readValue(response.body(), responseType);
|
||||
}
|
||||
throw new IOException("HTTP " + response.statusCode() + ": " + response.body());
|
||||
}
|
||||
|
||||
private <T> T postJson(String url, Object body, Class<T> responseType)
|
||||
throws IOException, InterruptedException {
|
||||
String jsonBody = objectMapper.writeValueAsString(body);
|
||||
HttpResponse<String> response = sendRequest("POST", url, jsonBody);
|
||||
if (response.statusCode() == 200 || response.statusCode() == 201) {
|
||||
return objectMapper.readValue(response.body(), responseType);
|
||||
}
|
||||
throw new IOException("HTTP " + response.statusCode() + ": " + response.body());
|
||||
}
|
||||
|
||||
private HttpResponse<String> sendRequest(String method, String url, String body)
|
||||
throws IOException, InterruptedException {
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("Authorization", authHeader)
|
||||
.header("Content-Type", "application/json")
|
||||
.method(method, body != null
|
||||
? HttpRequest.BodyPublishers.ofString(body)
|
||||
: HttpRequest.BodyPublishers.noBody());
|
||||
|
||||
HttpRequest request = builder.build();
|
||||
return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
}
|
||||
|
||||
private String buildBasicAuthHeader(String username, String password) {
|
||||
String credentials = username + ":" + password;
|
||||
String encoded = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
|
||||
return "Basic " + encoded;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.teterialosjuanjos.tfg.javafxclient.model;
|
||||
|
||||
import com.teterialosjuanjos.tfg.javafxclient.api.SyncServiceClient;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Gestiona la sesión actual: credenciales, cliente HTTP, estado de autenticación.
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
@Setter
|
||||
public class SessionManager {
|
||||
|
||||
private String baseUrl;
|
||||
private String username;
|
||||
private String password;
|
||||
private SyncServiceClient syncServiceClient;
|
||||
private boolean authenticated;
|
||||
|
||||
public SessionManager() {
|
||||
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
|
||||
*/
|
||||
public boolean authenticate(String baseUrl, String username, String password) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
|
||||
try {
|
||||
this.syncServiceClient = new SyncServiceClient(baseUrl, username, password);
|
||||
// Prueba conectividad
|
||||
syncServiceClient.checkHealth();
|
||||
this.authenticated = true;
|
||||
log.info("Authentication successful for user: {}", username);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Authentication failed: {}", e.getMessage());
|
||||
this.authenticated = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void logout() {
|
||||
this.authenticated = false;
|
||||
this.syncServiceClient = null;
|
||||
this.username = null;
|
||||
this.password = null;
|
||||
log.info("Logged out");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
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.util.AlertUtil;
|
||||
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.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.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Dashboard con estado de sincronizaciones y estadísticas.
|
||||
*/
|
||||
@Slf4j
|
||||
public class DashboardController {
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
private Label lastProductSyncLabel;
|
||||
private Label lastStockSyncLabel;
|
||||
private Label lastOrderSyncLabel;
|
||||
private Label successRateLabel;
|
||||
private Label totalSyncsLabel;
|
||||
|
||||
public DashboardController(SessionManager sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
public VBox createView() {
|
||||
VBox root = new VBox(20);
|
||||
root.setPadding(new Insets(20));
|
||||
root.setStyle("-fx-background-color: #fafafa;");
|
||||
|
||||
// Title
|
||||
Label title = new Label("Synchronization Dashboard");
|
||||
title.setStyle("-fx-font-size: 18; -fx-font-weight: bold;");
|
||||
|
||||
// Sync stats cards
|
||||
GridPane statsGrid = createStatsGrid();
|
||||
|
||||
// Action buttons
|
||||
HBox actionsBox = createActionsBox();
|
||||
|
||||
root.getChildren().addAll(
|
||||
title,
|
||||
statsGrid,
|
||||
actionsBox,
|
||||
new Label() // spacer
|
||||
);
|
||||
|
||||
// Auto-refresh cada 10 segundos
|
||||
startAutoRefresh();
|
||||
|
||||
// Carga inicial
|
||||
refreshDashboard();
|
||||
|
||||
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) {
|
||||
VBox card = new VBox(10);
|
||||
card.setPadding(new Insets(15));
|
||||
card.setStyle(
|
||||
"-fx-background-color: white; " +
|
||||
"-fx-border-color: " + color + "; " +
|
||||
"-fx-border-width: 3 0 0 0; " +
|
||||
"-fx-border-radius: 0;"
|
||||
);
|
||||
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 + ";");
|
||||
|
||||
Label valueLabel = new Label(value);
|
||||
valueLabel.setStyle("-fx-font-size: 16; -fx-font-weight: bold; -fx-text-fill: #333333;");
|
||||
|
||||
card.getChildren().addAll(titleLabel, valueLabel);
|
||||
|
||||
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(() -> {
|
||||
try {
|
||||
SyncServiceClient client = sessionManager.getSyncServiceClient();
|
||||
List<Map<String, Object>> logs = client.getLogs(null);
|
||||
|
||||
Platform.runLater(() -> updateDashboard(logs));
|
||||
} catch (Exception e) {
|
||||
log.error("Error refreshing dashboard: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private void updateDashboard(List<Map<String, Object>> 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<String, Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Success rate
|
||||
long successCount = logs.stream()
|
||||
.filter(l -> ((Number) l.get("itemsFailed")).longValue() == 0)
|
||||
.count();
|
||||
long successRate = (successCount * 100) / logs.size();
|
||||
successRateLabel.setText(successRate + "%");
|
||||
}
|
||||
|
||||
private void startAutoRefresh() {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.seconds(10), 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
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.util.AlertUtil;
|
||||
import javafx.application.Platform;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tab para logs de sincronización.
|
||||
*/
|
||||
@Slf4j
|
||||
public class LogsController {
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
private TableView<Map<String, Object>> logsTable;
|
||||
private TextArea errorDetailsArea;
|
||||
|
||||
public LogsController(SessionManager sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
public VBox createView() {
|
||||
VBox root = new VBox(15);
|
||||
root.setPadding(new Insets(20));
|
||||
root.setStyle("-fx-background-color: #fafafa;");
|
||||
|
||||
// Title
|
||||
Label title = new Label("Synchronization Logs");
|
||||
title.setStyle("-fx-font-size: 18; -fx-font-weight: bold;");
|
||||
|
||||
// Table
|
||||
logsTable = createLogsTable();
|
||||
VBox.setVgrow(logsTable, javafx.scene.layout.Priority.ALWAYS);
|
||||
logsTable.getSelectionModel().selectedItemProperty().addListener((obs, old, newVal) -> {
|
||||
if (newVal != null) {
|
||||
showLogDetails(newVal);
|
||||
}
|
||||
});
|
||||
|
||||
// Details panel
|
||||
VBox detailsPanel = createDetailsPanel();
|
||||
|
||||
// Filter & Actions
|
||||
HBox filterBox = createFilterBox();
|
||||
|
||||
root.getChildren().addAll(title, filterBox, logsTable, detailsPanel);
|
||||
|
||||
// Load initial data
|
||||
loadLogs(null);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private TableView<Map<String, Object>> createLogsTable() {
|
||||
TableView<Map<String, Object>> table = new TableView<>();
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
|
||||
table.setStyle("-fx-font-size: 11;");
|
||||
table.setPrefHeight(300);
|
||||
|
||||
// ID column
|
||||
TableColumn<Map<String, Object>, 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);
|
||||
|
||||
// Type column
|
||||
TableColumn<Map<String, Object>, 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<Map<String, Object>, 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<Map<String, Object>, 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<Map<String, Object>, 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<Map<String, Object>, 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<Map<String, Object>, 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;");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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;");
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
Label filterLabel = new Label("Filter by type:");
|
||||
|
||||
ComboBox<String> 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<Map<String, Object>> logs = client.getLogs(type);
|
||||
|
||||
ObservableList<Map<String, Object>> 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()));
|
||||
}
|
||||
});
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private void showLogDetails(Map<String, Object> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
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.util.AlertUtil;
|
||||
import javafx.application.Platform;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tab para pedidos: ver mapeos de PrestaShop → Dolibarr y disparar sync.
|
||||
*/
|
||||
@Slf4j
|
||||
public class OrdersController {
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
private final DashboardController dashboardController;
|
||||
private TableView<Map<String, Object>> ordersTable;
|
||||
|
||||
public OrdersController(SessionManager sessionManager, DashboardController dashboardController) {
|
||||
this.sessionManager = sessionManager;
|
||||
this.dashboardController = dashboardController;
|
||||
}
|
||||
|
||||
public VBox createView() {
|
||||
VBox root = new VBox(15);
|
||||
root.setPadding(new Insets(20));
|
||||
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;");
|
||||
|
||||
// Info box
|
||||
HBox infoBox = createInfoBox();
|
||||
|
||||
// Table
|
||||
ordersTable = createOrdersTable();
|
||||
VBox.setVgrow(ordersTable, javafx.scene.layout.Priority.ALWAYS);
|
||||
|
||||
// Action buttons
|
||||
HBox actionBox = createActionBox();
|
||||
|
||||
root.getChildren().addAll(title, infoBox, ordersTable, actionBox);
|
||||
|
||||
// Load initial data
|
||||
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<Map<String, Object>> createOrdersTable() {
|
||||
TableView<Map<String, Object>> table = new TableView<>();
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
|
||||
table.setStyle("-fx-font-size: 11;");
|
||||
|
||||
// PrestaShop Order ID column
|
||||
TableColumn<Map<String, Object>, 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);
|
||||
|
||||
// Dolibarr Order ID column
|
||||
TableColumn<Map<String, Object>, 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) : "—"
|
||||
);
|
||||
});
|
||||
dolOrderIdCol.setPrefWidth(120);
|
||||
|
||||
// Dolibarr Invoice ID column
|
||||
TableColumn<Map<String, Object>, 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) : "—"
|
||||
);
|
||||
});
|
||||
dolInvoiceIdCol.setPrefWidth(100);
|
||||
|
||||
// Status column
|
||||
TableColumn<Map<String, Object>, 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<Map<String, Object>, String>() {
|
||||
@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;");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Imported At column
|
||||
TableColumn<Map<String, Object>, 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);
|
||||
|
||||
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);
|
||||
|
||||
Label label = new Label("Actions:");
|
||||
label.setStyle("-fx-font-weight: bold;");
|
||||
|
||||
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);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
private void loadOrders() {
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
SyncServiceClient client = sessionManager.getSyncServiceClient();
|
||||
List<Map<String, Object>> mappings = client.getOrderMappings();
|
||||
|
||||
ObservableList<Map<String, Object>> items = FXCollections.observableArrayList(mappings);
|
||||
Platform.runLater(() -> ordersTable.setItems(items));
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading orders: {}", e.getMessage());
|
||||
Platform.runLater(() -> AlertUtil.showError("Error", "Failed to load orders: " + e.getMessage()));
|
||||
}
|
||||
});
|
||||
thread.setDaemon(true);
|
||||
thread.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.");
|
||||
|
||||
if (confirmAlert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) {
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
SyncServiceClient client = sessionManager.getSyncServiceClient();
|
||||
Map<String, Object> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
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.util.AlertUtil;
|
||||
import javafx.application.Platform;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Tab para productos: ver mapeos y disparar sync.
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProductsController {
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
private final DashboardController dashboardController;
|
||||
private TableView<Map<String, Object>> productsTable;
|
||||
|
||||
public ProductsController(SessionManager sessionManager, DashboardController dashboardController) {
|
||||
this.sessionManager = sessionManager;
|
||||
this.dashboardController = dashboardController;
|
||||
}
|
||||
|
||||
public VBox createView() {
|
||||
VBox root = new VBox(15);
|
||||
root.setPadding(new Insets(20));
|
||||
root.setStyle("-fx-background-color: #fafafa;");
|
||||
|
||||
// Title
|
||||
Label title = new Label("Product Mappings");
|
||||
title.setStyle("-fx-font-size: 18; -fx-font-weight: bold;");
|
||||
|
||||
// Filter & Actions
|
||||
HBox filterBox = createFilterBox();
|
||||
|
||||
// Table
|
||||
productsTable = createProductsTable();
|
||||
VBox.setVgrow(productsTable, javafx.scene.layout.Priority.ALWAYS);
|
||||
|
||||
// Action buttons
|
||||
HBox actionBox = createActionBox();
|
||||
|
||||
root.getChildren().addAll(title, filterBox, productsTable, actionBox);
|
||||
|
||||
// Load initial data
|
||||
loadProducts(null);
|
||||
|
||||
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);
|
||||
|
||||
Label filterLabel = new Label("Filter by status:");
|
||||
|
||||
ComboBox<String> 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);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
private TableView<Map<String, Object>> createProductsTable() {
|
||||
TableView<Map<String, Object>> table = new TableView<>();
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
|
||||
table.setStyle("-fx-font-size: 11;");
|
||||
|
||||
// SKU column
|
||||
TableColumn<Map<String, Object>, 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);
|
||||
|
||||
// Dolibarr ID column
|
||||
TableColumn<Map<String, Object>, String> dolIdCol = new TableColumn<>("Dolibarr ID");
|
||||
dolIdCol.setCellValueFactory(cf -> {
|
||||
Object value = cf.getValue().get("dolibarrId");
|
||||
return new javafx.beans.property.SimpleObjectProperty<>(String.valueOf(value));
|
||||
});
|
||||
dolIdCol.setPrefWidth(100);
|
||||
|
||||
// PrestaShop ID column
|
||||
TableColumn<Map<String, Object>, 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);
|
||||
|
||||
// Status column
|
||||
TableColumn<Map<String, Object>, 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<Map<String, Object>, String>() {
|
||||
@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;");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Error message column
|
||||
TableColumn<Map<String, Object>, 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);
|
||||
});
|
||||
errorCol.setPrefWidth(200);
|
||||
|
||||
table.getColumns().addAll(skuCol, dolIdCol, psIdCol, statusCol, errorCol);
|
||||
|
||||
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_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 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());
|
||||
|
||||
box.getChildren().addAll(syncButton, stockButton);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
private void loadProducts(String status) {
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
SyncServiceClient client = sessionManager.getSyncServiceClient();
|
||||
List<Map<String, Object>> mappings = client.getProductMappings(status);
|
||||
|
||||
ObservableList<Map<String, Object>> items = FXCollections.observableArrayList(mappings);
|
||||
Platform.runLater(() -> productsTable.setItems(items));
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading products: {}", e.getMessage());
|
||||
Platform.runLater(() -> AlertUtil.showError("Error", "Failed to load products: " + e.getMessage()));
|
||||
}
|
||||
});
|
||||
thread.setDaemon(true);
|
||||
thread.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<String, Object> 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 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.");
|
||||
|
||||
if (confirmAlert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) {
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
SyncServiceClient client = sessionManager.getSyncServiceClient();
|
||||
Map<String, Object> result = 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
package com.teterialosjuanjos.tfg.javafxclient.ui.stages;
|
||||
|
||||
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.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Pantalla de login para autenticación contra el sync-service.
|
||||
*/
|
||||
@Slf4j
|
||||
public class LoginStage {
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
private final Stage primaryStage;
|
||||
private Stage loginWindow;
|
||||
|
||||
public LoginStage(SessionManager sessionManager, Stage primaryStage) {
|
||||
this.sessionManager = sessionManager;
|
||||
this.primaryStage = primaryStage;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
loginWindow = new Stage();
|
||||
loginWindow.setTitle("TFG Sync Service — Login");
|
||||
loginWindow.setWidth(400);
|
||||
loginWindow.setHeight(500);
|
||||
loginWindow.setResizable(false);
|
||||
|
||||
VBox root = createLoginForm();
|
||||
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add(getClass().getResource("/styles.css").toExternalForm());
|
||||
|
||||
loginWindow.setScene(scene);
|
||||
loginWindow.setOnCloseRequest(e -> Platform.exit());
|
||||
loginWindow.show();
|
||||
}
|
||||
|
||||
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;");
|
||||
|
||||
// Title
|
||||
Label title = new Label("Sync Service Client");
|
||||
title.setStyle("-fx-font-size: 24; -fx-font-weight: bold; -fx-text-fill: #1976d2;");
|
||||
|
||||
Label subtitle = new Label("Dolibarr ↔ PrestaShop Integration");
|
||||
subtitle.setStyle("-fx-font-size: 12; -fx-text-fill: #666666;");
|
||||
|
||||
// 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");
|
||||
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;");
|
||||
|
||||
// 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()
|
||||
));
|
||||
|
||||
// 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");
|
||||
}
|
||||
});
|
||||
|
||||
// Separator
|
||||
Separator separator = new Separator();
|
||||
|
||||
// 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;");
|
||||
|
||||
form.getChildren().addAll(
|
||||
title,
|
||||
subtitle,
|
||||
new Separator(),
|
||||
urlLabel,
|
||||
urlField,
|
||||
usernameLabel,
|
||||
usernameField,
|
||||
passwordLabel,
|
||||
passwordField,
|
||||
separator,
|
||||
devModeCheckBox,
|
||||
new Region(), // spacer
|
||||
loginButton,
|
||||
infoLabel
|
||||
);
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ejecuta en background para no freezear UI
|
||||
Thread loginThread = new Thread(() -> {
|
||||
try {
|
||||
boolean success = sessionManager.authenticate(baseUrl, username, password);
|
||||
Platform.runLater(() -> {
|
||||
if (success) {
|
||||
AlertUtil.showInfo("Success", "Authentication successful!");
|
||||
loginWindow.close();
|
||||
openMainStage();
|
||||
} else {
|
||||
AlertUtil.showError("Login Failed",
|
||||
"Could not authenticate. Check credentials and URL.");
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
Platform.runLater(() -> {
|
||||
AlertUtil.showError("Connection Error", ex.getMessage());
|
||||
});
|
||||
}
|
||||
});
|
||||
loginThread.setDaemon(true);
|
||||
loginThread.start();
|
||||
}
|
||||
|
||||
private void openMainStage() {
|
||||
MainStage mainStage = new MainStage(sessionManager, primaryStage);
|
||||
mainStage.show();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package com.teterialosjuanjos.tfg.javafxclient.ui.stages;
|
||||
|
||||
import com.teterialosjuanjos.tfg.javafxclient.model.SessionManager;
|
||||
import com.teterialosjuanjos.tfg.javafxclient.ui.controllers.*;
|
||||
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.stage.Stage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Pantalla principal con navegación por tabs y dashboard.
|
||||
*/
|
||||
@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;
|
||||
this.primaryStage = primaryStage;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
mainWindow = new Stage();
|
||||
mainWindow.setTitle("TFG Sync Service — Dashboard");
|
||||
mainWindow.setWidth(1200);
|
||||
mainWindow.setHeight(700);
|
||||
|
||||
BorderPane root = createMainLayout();
|
||||
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add(getClass().getResource("/styles.css").toExternalForm());
|
||||
|
||||
mainWindow.setScene(scene);
|
||||
mainWindow.show();
|
||||
}
|
||||
|
||||
private BorderPane createMainLayout() {
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
// Top: Header con usuario y logout
|
||||
root.setTop(createHeader());
|
||||
|
||||
// Center: Tabs
|
||||
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;");
|
||||
|
||||
HBox topBar = new HBox(10);
|
||||
topBar.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
Label title = new Label("Sync Service Control Panel");
|
||||
title.setStyle("-fx-font-size: 18; -fx-font-weight: bold; -fx-text-fill: white;");
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, javafx.scene.layout.Priority.ALWAYS);
|
||||
|
||||
Label userLabel = new Label("User: " + sessionManager.getUsername());
|
||||
userLabel.setStyle("-fx-font-size: 12; -fx-text-fill: white;");
|
||||
|
||||
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);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private TabPane createTabPane() {
|
||||
TabPane tabPane = new TabPane();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.teterialosjuanjos.tfg.javafxclient.util;
|
||||
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
|
||||
/**
|
||||
* Utilidades para mostrar diálogos de alerta.
|
||||
*/
|
||||
public class AlertUtil {
|
||||
|
||||
public static void showInfo(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
public static void showWarn(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
public static void showError(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%-5level] %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
<logger name="com.teterialosjuanjos.tfg.javafx_client" level="DEBUG"/>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/* 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;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%-5level] %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
<logger name="com.teterialosjuanjos.tfg.javafx_client" level="DEBUG"/>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/* 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;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/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
|
||||
Loading…
Reference in New Issue