]> Git Server - tankstelle.git/commitdiff
unstable but at least it starts
authorRobin Cheney <cheneyr@eternal.ddnss.de>
Thu, 20 Nov 2025 17:38:00 +0000 (18:38 +0100)
committerRobin Cheney <cheneyr@eternal.ddnss.de>
Thu, 20 Nov 2025 17:38:00 +0000 (18:38 +0100)
21 files changed:
pom.xml
src/main/java/de/diejungsvondertanke/tankstelle/FuelStation.java
src/main/java/de/diejungsvondertanke/tankstelle/FuelStationUI.java.old [moved from src/main/java/de/diejungsvondertanke/tankstelle/FuelStationUI.java with 100% similarity]
src/main/java/de/diejungsvondertanke/tankstelle/Main.java
src/main/java/de/diejungsvondertanke/tankstelle/controllers/ControllerRegistry.java.old [new file with mode: 0644]
src/main/java/de/diejungsvondertanke/tankstelle/controllers/FuelStationUIController.java [new file with mode: 0644]
src/main/java/de/diejungsvondertanke/tankstelle/controllers/NewStationTabController.java [new file with mode: 0644]
src/main/java/de/diejungsvondertanke/tankstelle/controllers/OverviewTabController.java [new file with mode: 0644]
src/main/java/de/diejungsvondertanke/tankstelle/controllers/PriceTabController.java [new file with mode: 0644]
src/main/java/de/diejungsvondertanke/tankstelle/controllers/ResultTabController.java [new file with mode: 0644]
src/main/java/de/diejungsvondertanke/tankstelle/controllers/SearchTabController.java [new file with mode: 0644]
src/main/java/de/diejungsvondertanke/tankstelle/controllers/StockTabController.java [new file with mode: 0644]
src/main/java/de/diejungsvondertanke/tankstelle/ui/JFX.java [new file with mode: 0644]
src/main/java/module-info.java [new file with mode: 0644]
src/main/resources/ui/FuelStationUI.fxml [new file with mode: 0644]
src/main/resources/ui/NewStationTab.fxml [new file with mode: 0644]
src/main/resources/ui/OverviewTab.fxml [new file with mode: 0644]
src/main/resources/ui/PriceTab.fxml [new file with mode: 0644]
src/main/resources/ui/ResultTab.fxml [new file with mode: 0644]
src/main/resources/ui/SearchTab.fxml [new file with mode: 0644]
src/main/resources/ui/StockTab.fxml [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index c35dd0ab4743f34648badc6a18ffa16d1de43208..f1179cb1645842006f0ca95fe686c377448cd156 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -6,18 +6,82 @@
     <groupId>de.diejungsvondertanke.tankstelle</groupId>
     <artifactId>tankstelle</artifactId>
     <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
     <build>
         <plugins>
+            <!-- Adds the mainClass to the jar so it will run outside -->
             <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
-                <version>3.3.0</version>
+                <version>3.1.0</version>
                 <configuration>
                     <archive>
                         <manifest>
-                            <mainClass>de.diejungsvondertanke.tankstelle.Main</mainClass>
+                            <mainClass>${mainClass}</mainClass>
+                            <!-- <mainClass>de.diejungsvondertanke.tankstelle.Main</mainClass> -->
                         </manifest>
                     </archive>
+                    <outputDirectory>${project.build.directory}/modules</outputDirectory>
+                </configuration>
+            </plugin>
+
+            <!-- sets up the version of Java you are running -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.0</version>
+                <configuration>
+                    <release>${java.version}</release>
+                </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.ow2.asm</groupId>
+                        <artifactId>asm</artifactId>
+                        <version>6.2.1</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+
+            <!-- Copies the depend FX files to your program  -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/modules</outputDirectory>
+                            <includeScope>runtime</includeScope>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Maven Compiler -->
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+
+            <!-- Maven Exec Plugin to run JavaFX app -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <version>3.1.0</version>
+                <configuration>
+                    <mainClass>${main.Class}</mainClass>
+                    <commandlineArgs>
+                        --module-path
+                        ${java.home}/lib
+                        --add-modules=javafx.controls,javafx.fxml
+                    </commandlineArgs>
                 </configuration>
             </plugin>
         </plugins>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <maven.compiler.source>25</maven.compiler.source>
         <maven.compiler.target>25</maven.compiler.target>
+        <java.version>20</java.version>
+        <javafx.version>23.0.2</javafx.version>
+        <main.Class>de.diejungsvondertanke.tankstelle.ui.JFX</main.Class>
     </properties>
+    <dependencies>
+        <!-- JavaFX modules -->
+        <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>
+
+        <!-- Optional: if you use TableView + properties -->
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-base</artifactId>
+            <version>${javafx.version}</version>
+        </dependency>
+    </dependencies>
 
 </project>
\ No newline at end of file
index c2c5fe389fca9bfaea2a7bfb60fab324d2f047b2..a3c604206ea069d99e98bb7065250cf0166c8b9d 100644 (file)
@@ -15,7 +15,7 @@ import de.diejungsvondertanke.tankstelle.error.NoSuchFuelTypeError;
  * @author Robin Cheney
  * @author Nils Göbbert
  */
-abstract class FuelStation {
+public abstract class FuelStation {
     /**
      * Number of employees of this fuel station
      */
index 0e0d38c377c023631112292058f9a49b0517cda5..0ab25e4e2ced467a69f6a53fc3492b159c82d10e 100644 (file)
@@ -29,7 +29,7 @@ public class Main {
      * create an array list to store the fuel stations while the program is running
      * add the initial array of fuel stations to the List
      */
-    static ArrayList<FuelStation> fuelStations = new ArrayList<FuelStation>(Arrays.asList(initialFuelStations));
+    public static ArrayList<FuelStation> fuelStations = new ArrayList<FuelStation>(Arrays.asList(initialFuelStations));
 
     /**
      * Main method
@@ -37,10 +37,10 @@ public class Main {
      * @param args Program arguments (not in use)
      */
     public static void main(String[] args) {
-        javax.swing.SwingUtilities.invokeLater(() -> {
-            FuelStationUI ui = new FuelStationUI();
-            ui.setVisible(true);
-        });
+        // javax.swing.SwingUtilities.invokeLater(() -> {
+        //     FuelStationUI ui = new FuelStationUI();
+        //     ui.setVisible(true);
+        // });
     }
 
     /**
diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ControllerRegistry.java.old b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ControllerRegistry.java.old
new file mode 100644 (file)
index 0000000..010b211
--- /dev/null
@@ -0,0 +1,13 @@
+package de.diejungsvondertanke.tankstelle.controllers;
+
+public class ControllerRegistry {
+    private static FuelStationUIController main;
+
+    public static void registerMain(FuelStationUIController controller) {
+        main = controller;
+    }
+
+    public static FuelStationUIController getMain() {
+        return main;
+    }
+}
diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/FuelStationUIController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/FuelStationUIController.java
new file mode 100644 (file)
index 0000000..78f17b0
--- /dev/null
@@ -0,0 +1,179 @@
+package de.diejungsvondertanke.tankstelle.controllers;
+
+import java.io.IOException;
+import java.util.function.Consumer;
+
+import de.diejungsvondertanke.tankstelle.*;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.control.*;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Pane;
+
+public class FuelStationUIController {
+
+    // @FXML
+    // public ComboBox<String> comboFuelStations;
+
+    // @FXML
+    // public ComboBox<FuelType> comboFuelTypes;
+
+    // @FXML
+    // private TabPane tabPane;
+
+    // // child controllers
+    // @FXML
+    // private HBox resultTab;
+
+    // @FXML
+    // private HBox priceTab;
+
+    // @FXML
+    // private HBox stockTab;
+
+    // @FXML
+    // private HBox searchTab;
+
+    // @FXML
+    // private HBox newStationTab;
+
+    // @FXML
+    // private HBox overviewTab;
+
+    // @FXML
+    // private TextArea outputArea;
+
+    @FXML
+    public ComboBox<String> comboFuelStations;
+    @FXML
+    public ComboBox<FuelType> comboFuelTypes;
+    @FXML
+    private TabPane tabPane;
+    @FXML
+    private TextArea outputArea;
+
+    // Placeholder containers for the included tabs
+    @FXML
+    private HBox resultTabContainer;
+    @FXML
+    private GridPane priceTabContainer;
+    @FXML
+    private GridPane stockTabContainer;
+    @FXML
+    private HBox searchTabContainer;
+    @FXML
+    private GridPane newStationTabContainer;
+    @FXML
+    private BorderPane overviewTabContainer;
+
+    // Controllers of included tabs
+    private ResultTabController resultTabController;
+    private PriceTabController priceTabController;
+    private StockTabController stockTabController;
+    private SearchTabController searchTabController;
+    private NewStationTabController newStationTabController;
+    private OverviewTabController overviewTabController;
+
+    @FXML
+    public void initialize() {
+        // Load all tabs manually and keep controllers
+        comboFuelTypes.getItems().setAll(FuelType.values());
+        // loadTab("/ui/ResultTab.fxml", resultTabContainer,
+        // (ResultTabController controller) -> resultTabController = controller);
+        // loadTab("/ui/PriceTab.fxml", priceTabContainer,
+        // (PriceTabController controller) -> priceTabController = controller);
+        // loadTab("/ui/StockTab.fxml", stockTabContainer,
+        // (StockTabController controller) -> stockTabController = controller);
+        // loadTab("/ui/SearchTab.fxml", searchTabContainer,
+        // (SearchTabController controller) -> searchTabController = controller);
+        // loadTab("/ui/NewStationTab.fxml", newStationTabContainer,
+        // (NewStationTabController controller) -> newStationTabController =
+        // controller);
+        // loadTab("/ui/OverviewTab.fxml", overviewTabContainer,
+        // (OverviewTabController controller) -> overviewTabController = controller);
+
+        try {
+            loadTab("/ui/ResultTab.fxml", resultTabContainer, (ResultTabController c) -> resultTabController = c);
+            loadTab("/ui/PriceTab.fxml", priceTabContainer, (PriceTabController c) -> priceTabController = c);
+            loadTab("/ui/StockTab.fxml", stockTabContainer, (StockTabController c) -> stockTabController = c);
+            loadTab("/ui/SearchTab.fxml", searchTabContainer, (SearchTabController c) -> searchTabController = c);
+            loadTab("/ui/NewStationTab.fxml", newStationTabContainer,
+                    (NewStationTabController c) -> newStationTabController = c);
+            loadTab("/ui/OverviewTab.fxml", overviewTabContainer,
+                    (OverviewTabController c) -> overviewTabController = c);
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+    }
+
+    // Generic loader helper
+    // private <T> void loadTab(String fxmlPath, Parent placeholder,
+    // java.util.function.Consumer<T> controllerSetter) {
+    // try {
+    // FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlPath));
+    // Parent content = loader.load();
+    // T controller = loader.getController();
+    // controllerSetter.accept(controller);
+    // placeholder.getChildren().setAll(content);
+    // } catch (IOException e) {
+    // e.printStackTrace();
+    // }
+    // }
+
+    private <T> void loadTab(String fxmlPath, Parent container, Consumer<T> controllerConsumer) throws IOException {
+        FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlPath));
+        Parent loaded = loader.load();
+        T controller = loader.getController();
+
+        // Add loaded node to the container
+        if (container instanceof Pane pane) {
+            pane.getChildren().setAll(loaded);
+        } else if (container instanceof ScrollPane scroll) {
+            scroll.setContent(loaded);
+        } else if (container instanceof BorderPane border) {
+            border.setCenter(loaded);
+        } else {
+            throw new IllegalArgumentException("Unsupported container type: " + container.getClass());
+        }
+
+        controllerConsumer.accept(controller);
+    }
+
+    public FuelStation getSelectedStation() {
+        int idx = comboFuelStations.getSelectionModel().getSelectedIndex();
+        if (idx < 0 || idx >= Main.fuelStations.size())
+            return null;
+        return Main.fuelStations.get(idx);
+    }
+
+    public void refreshStationNames() {
+        comboFuelStations.getItems().setAll(
+                Main.fuelStations.stream()
+                        .map(this::getDisplayName)
+                        .toList());
+    }
+
+    public String getDisplayName(FuelStation fs) {
+        int index = Main.fuelStations.indexOf(fs);
+        if (index >= 0) {
+            return "Station " + (index + 1) + " (" + fs.getClass().getSimpleName() + ")";
+        }
+        return fs.getClass().getSimpleName();
+    }
+
+    public void appendOutput(String text) {
+        outputArea.appendText(text + "\n\n");
+    }
+
+    public void showError(String msg) {
+        Alert alert = new Alert(Alert.AlertType.ERROR);
+        alert.setHeaderText("Error");
+        alert.setContentText(msg);
+        alert.showAndWait();
+    }
+}
diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/NewStationTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/NewStationTabController.java
new file mode 100644 (file)
index 0000000..603bec9
--- /dev/null
@@ -0,0 +1,48 @@
+package de.diejungsvondertanke.tankstelle.controllers;
+
+import de.diejungsvondertanke.tankstelle.Main;
+import de.diejungsvondertanke.tankstelle.ui.JFX;
+import javafx.fxml.FXML;
+import javafx.scene.control.*;
+
+public class NewStationTabController {
+
+    @FXML
+    private RadioButton rbSmall, rbMedium, rbLarge;
+    @FXML
+    private TextField txtAttr;
+
+    private FuelStationUIController parentController;
+
+    // Called by parent after full initialization
+    public void setParentController(FuelStationUIController parent) {
+        this.parentController = parent;
+    }
+
+    @FXML
+    private void add() {
+        try {
+            if (rbSmall.isSelected()) {
+                short vending = Short.parseShort(txtAttr.getText().trim());
+                Main.addNewFuelStation(vending);
+                parentController.appendOutput("Added small station (" + vending + " vending machines).");
+
+            } else if (rbMedium.isSelected()) {
+                float size = Float.parseFloat(txtAttr.getText().replace(",", "."));
+                Main.addNewFuelStation(size);
+                parentController.appendOutput("Added medium station (" + size + " m²).");
+
+            } else if (rbLarge.isSelected()) {
+                String company = txtAttr.getText().trim();
+                Main.addNewFuelStation(company);
+                parentController.appendOutput("Added large station (" + company + ").");
+            }
+
+            parentController.refreshStationNames();
+            txtAttr.clear();
+
+        } catch (Exception e) {
+            parentController.showError(e.getMessage());
+        }
+    }
+}
diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/OverviewTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/OverviewTabController.java
new file mode 100644 (file)
index 0000000..2ce3c46
--- /dev/null
@@ -0,0 +1,75 @@
+package de.diejungsvondertanke.tankstelle.controllers;
+
+import de.diejungsvondertanke.tankstelle.*;
+import de.diejungsvondertanke.tankstelle.ui.JFX;
+import javafx.beans.property.SimpleFloatProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.fxml.FXML;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+
+public class OverviewTabController {
+
+    @FXML
+    private TableView<FuelRow> table;
+
+    @FXML
+    private TableColumn<FuelRow, String> colStation;
+    @FXML
+    private TableColumn<FuelRow, String> colType;
+    @FXML
+    private TableColumn<FuelRow, String> colFuel;
+    @FXML
+    private TableColumn<FuelRow, Float> colAmount;
+    @FXML
+    private TableColumn<FuelRow, Integer> colCapacity;
+    @FXML
+    private TableColumn<FuelRow, Float> colPrice;
+
+    private FuelStationUIController parentController;
+
+    // Called by parent after full initialization
+    public void setParentController(FuelStationUIController parent) {
+        this.parentController = parent;
+    }
+
+    @FXML
+    public void initialize() {
+        colStation.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().station()));
+        colType.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().type()));
+        colFuel.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().fuel()));
+        colAmount.setCellValueFactory(data -> new SimpleFloatProperty(data.getValue().amount()).asObject());
+        colCapacity.setCellValueFactory(data -> new SimpleIntegerProperty(data.getValue().capacity()).asObject());
+        colPrice.setCellValueFactory(data -> new SimpleFloatProperty(data.getValue().price()).asObject());
+
+        refresh();
+    }
+
+    @FXML
+    public void refresh() {
+        table.getItems().clear();
+
+        for (FuelStation station : Main.fuelStations) {
+            for (Fuel f : station.fuels) {
+                table.getItems().add(
+                        new FuelRow(
+                                parentController.getDisplayName(station),
+                                station.getClass().getSimpleName(),
+                                f.FUEL_TYPE.toString(),
+                                f.getStored_amount(),
+                                f.CAPACITY,
+                                f.getPrice()));
+            }
+        }
+    }
+
+    public record FuelRow(
+            String station,
+            String type,
+            String fuel,
+            float amount,
+            int capacity,
+            float price) {
+    }
+}
diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/PriceTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/PriceTabController.java
new file mode 100644 (file)
index 0000000..2a2c616
--- /dev/null
@@ -0,0 +1,47 @@
+package de.diejungsvondertanke.tankstelle.controllers;
+
+import de.diejungsvondertanke.tankstelle.FuelStation;
+import de.diejungsvondertanke.tankstelle.FuelType;
+import de.diejungsvondertanke.tankstelle.error.NoSuchFuelTypeError;
+import de.diejungsvondertanke.tankstelle.ui.JFX;
+import javafx.fxml.FXML;
+import javafx.scene.control.TextField;
+
+public class PriceTabController {
+
+    @FXML
+    private TextField txtPrice;
+    
+    private FuelStationUIController parentController;
+
+    // Called by parent after full initialization
+    public void setParentController(FuelStationUIController parent) {
+        this.parentController = parent;
+    }
+
+    @FXML
+    private void save() {
+        FuelStation station = parentController.getSelectedStation();
+        FuelType type = parentController.comboFuelTypes.getValue();
+
+        if (station == null) {
+            parentController.showError("Select a station.");
+            return;
+        }
+
+        try {
+            float price = Float.parseFloat(txtPrice.getText().replace(",", "."));
+            station.set_price(type, price);
+
+            parentController.appendOutput("Price of %s at %s changed to %.3f €/L"
+                    .formatted(type, parentController.getDisplayName(station), price));
+
+            txtPrice.clear();
+
+        } catch (NumberFormatException ex) {
+            parentController.showError("Invalid number.");
+        } catch (NoSuchFuelTypeError ex) {
+            parentController.showError("Fuel type unavailable.");
+        }
+    }
+}
diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ResultTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ResultTabController.java
new file mode 100644 (file)
index 0000000..c6619c3
--- /dev/null
@@ -0,0 +1,88 @@
+package de.diejungsvondertanke.tankstelle.controllers;
+
+import de.diejungsvondertanke.tankstelle.*;
+import de.diejungsvondertanke.tankstelle.error.NoSuchFuelTypeError;
+import de.diejungsvondertanke.tankstelle.ui.JFX;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListView;
+
+public class ResultTabController {
+
+    @FXML
+    private ListView<String> listFuelStations;
+    private FuelStationUIController parentController;
+
+    public void setParentController(FuelStationUIController parentController) {
+        this.parentController = parentController;
+    }
+
+    public void refreshList() {
+        if (parentController != null && parentController.comboFuelStations != null) {
+            listFuelStations.getItems().setAll(parentController.comboFuelStations.getItems());
+        }
+    }
+
+    // Example action using parent
+    @FXML
+    private void handleTotalPrice() {
+        if (parentController != null) {
+            parentController.appendOutput("Total price calculation here...");
+        }
+    }
+
+    @FXML
+    private void handleHighestPrice() {
+        FuelType type = parentController.comboFuelTypes.getValue();
+        try {
+            FuelStation fs = Main.getHighestPrice(type);
+            parentController.appendOutput("Highest price for %s: %s"
+                    .formatted(type, parentController.getDisplayName(fs)));
+        } catch (NoSuchFuelTypeError ex) {
+            parentController.showError("Fuel type unavailable.");
+        }
+    }
+
+    @FXML
+    private void handleHighestTotalValue() {
+        try {
+            FuelStation fs = Main.getHighestAccumulatedValue();
+            parentController.appendOutput("Highest total value: " + parentController.getDisplayName(fs));
+        } catch (NoSuchFuelTypeError ex) {
+            parentController.showError("Calculation error.");
+        }
+    }
+
+    @FXML
+    private void handleStock() {
+        FuelType type = parentController.comboFuelTypes.getValue();
+        try {
+            FuelStation max = Main.getHighestStoredAmount(type);
+            FuelStation min = Main.getLowestStoredAmount(type);
+            parentController.appendOutput("""
+                        Stock for %s:
+                         - Highest: %s
+                         - Lowest: %s
+                    """.formatted(type, parentController.getDisplayName(max), parentController.getDisplayName(min)));
+        } catch (NoSuchFuelTypeError e) {
+            parentController.showError("Fuel type unavailable.");
+        }
+    }
+
+    @FXML
+    private void handleTotalStock() {
+        FuelType type = parentController.comboFuelTypes.getValue();
+        var items = listFuelStations.getSelectionModel().getSelectedIndices();
+
+        if (items.isEmpty()) {
+            parentController.showError("Select at least one station.");
+            return;
+        }
+
+        FuelStation[] stations = items.stream()
+                .map(i -> Main.fuelStations.get(i))
+                .toArray(FuelStation[]::new);
+
+        float total = Main.getTotalStockLevelOfFuel(type, stations);
+        parentController.appendOutput("Total for %s: %.2f L".formatted(type, total));
+    }
+}
diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/SearchTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/SearchTabController.java
new file mode 100644 (file)
index 0000000..8e86747
--- /dev/null
@@ -0,0 +1,33 @@
+package de.diejungsvondertanke.tankstelle.controllers;
+
+import de.diejungsvondertanke.tankstelle.*;
+import de.diejungsvondertanke.tankstelle.ui.JFX;
+import de.diejungsvondertanke.tankstelle.error.NoSuchFuelTypeError;
+import javafx.fxml.FXML;
+
+public class SearchTabController {
+
+    private FuelStationUIController parentController;
+
+    // Called by parent after full initialization
+    public void setParentController(FuelStationUIController parent) {
+        this.parentController = parent;
+    }
+
+    @FXML
+    private void search() {
+        FuelType type = parentController.comboFuelTypes.getValue();
+
+        StringBuilder sb = new StringBuilder("Stations with " + type + ":\n");
+
+        for (FuelStation station : Main.fuelStations) {
+            try {
+                station.getStored_amount(type);
+                sb.append(" - ").append(parentController.getDisplayName(station)).append("\n");
+            } catch (NoSuchFuelTypeError ignore) {
+            }
+        }
+
+        parentController.appendOutput(sb.toString());
+    }
+}
diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/StockTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/StockTabController.java
new file mode 100644 (file)
index 0000000..2224267
--- /dev/null
@@ -0,0 +1,73 @@
+package de.diejungsvondertanke.tankstelle.controllers;
+
+import de.diejungsvondertanke.tankstelle.*;
+import de.diejungsvondertanke.tankstelle.ui.JFX;
+import de.diejungsvondertanke.tankstelle.error.NoSuchFuelTypeError;
+import javafx.fxml.FXML;
+import javafx.scene.control.*;
+
+public class StockTabController {
+
+    @FXML
+    private TextField txtAmount;
+    @FXML
+    private RadioButton rbAbsolute;
+    @FXML
+    private RadioButton rbDelta;
+
+    private FuelStationUIController parentController;
+
+    // Called by parent after full initialization
+    public void setParentController(FuelStationUIController parent) {
+        this.parentController = parent;
+    }
+
+    private int getCapacity(FuelStation station, FuelType type) throws NoSuchFuelTypeError {
+        for (Fuel f : station.fuels) {
+            if (f.FUEL_TYPE == type)
+                return f.CAPACITY;
+        }
+        throw new NoSuchFuelTypeError("Fuel type not available");
+    }
+
+    @FXML
+    private void save() {
+        FuelStation station = parentController.getSelectedStation();
+        FuelType type = parentController.comboFuelTypes.getValue();
+
+        if (station == null) {
+            parentController.showError("Select a station.");
+            return;
+        }
+
+        try {
+            float value = Float.parseFloat(txtAmount.getText().replace(",", "."));
+            int capacity = getCapacity(station, type);
+            float current = station.getStored_amount(type);
+
+            if (rbAbsolute.isSelected()) {
+                if (value < 0 || value > capacity) {
+                    parentController.showError("Invalid amount.");
+                    return;
+                }
+                station.set_stored_amount(value, type);
+                parentController.appendOutput("Stock of %s at %s set to %.2f L"
+                        .formatted(type, parentController.getDisplayName(station), value));
+            } else {
+                float updated = current + value;
+                if (updated < 0 || updated > capacity) {
+                    parentController.showError("Out of capacity bounds.");
+                    return;
+                }
+                station.add_stored_amount(value, type);
+                parentController.appendOutput("Stock of %s at %s changed by %.2f L"
+                        .formatted(type, parentController.getDisplayName(station), value));
+            }
+
+            txtAmount.clear();
+
+        } catch (Exception ex) {
+            parentController.showError(ex.getMessage());
+        }
+    }
+}
diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/ui/JFX.java b/src/main/java/de/diejungsvondertanke/tankstelle/ui/JFX.java
new file mode 100644 (file)
index 0000000..5e2d1cf
--- /dev/null
@@ -0,0 +1,42 @@
+package de.diejungsvondertanke.tankstelle.ui;
+
+import java.io.IOException;
+
+// import de.diejungsvondertanke.tankstelle.controllers.ControllerRegistry;
+import de.diejungsvondertanke.tankstelle.controllers.FuelStationUIController;
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+public class JFX extends Application {
+
+    static Stage stage;
+
+    public static FuelStationUIController controller;
+
+    static Parent parent;
+
+    @Override
+    public void start(Stage s) throws Exception {
+        stage = s;
+        Parent parent = loadFXML("FuelStationUI");
+        Scene scene = new Scene(parent);
+
+        stage.setScene(scene);
+        stage.setTitle("Fuel Station Management System");
+        stage.show();
+    }
+
+    public static void main(String[] args) {
+        launch(args);
+    }
+
+    private static Parent loadFXML(String fxml) throws IOException {
+        FXMLLoader fxmlLoader = new FXMLLoader(JFX.class.getResource("/ui/" + fxml + ".fxml"));
+        parent = fxmlLoader.load();
+        controller = fxmlLoader.getController();
+        return parent;
+    }
+}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
new file mode 100644 (file)
index 0000000..e850031
--- /dev/null
@@ -0,0 +1,10 @@
+module tankstelle {
+    requires javafx.controls;
+    requires javafx.fxml;
+    requires javafx.graphics;
+    requires javafx.base;
+
+    opens de.diejungsvondertanke.tankstelle.ui to javafx.fxml, javafx.graphics;
+    opens de.diejungsvondertanke.tankstelle.controllers to javafx.fxml, javafx.graphics;
+    // exports de.diejungsvondertanke.tankstelle;
+}
diff --git a/src/main/resources/ui/FuelStationUI.fxml b/src/main/resources/ui/FuelStationUI.fxml
new file mode 100644 (file)
index 0000000..1856b65
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.layout.*?>
+<?import javafx.scene.control.*?>
+
+<BorderPane xmlns:fx="http://javafx.com/fxml"
+    fx:controller="de.diejungsvondertanke.tankstelle.controllers.FuelStationUIController"
+    prefWidth="900" prefHeight="700">
+
+    <!-- TOP — fuel station + fuel type selectors -->
+    <top>
+        <HBox spacing="10" styleClass="top-bar" alignment="CENTER_LEFT">
+            <padding>
+                <Insets top="10" right="10" bottom="10" left="10" />
+            </padding>
+            <Label text="Fuel station:" />
+            <ComboBox fx:id="comboFuelStations" prefWidth="200" />
+            <Label text="Fuel type:" />
+            <ComboBox fx:id="comboFuelTypes" prefWidth="150" />
+        </HBox>
+    </top>
+
+    <!-- CENTER — tabs -->
+    <center>
+        <TabPane fx:id="tabPane">
+            <tabs>
+                <Tab text="Result" closable="false">
+                    <HBox fx:id="resultTabContainer" />
+                </Tab>
+                <Tab text="Change price" closable="false">
+                    <GridPane fx:id="priceTabContainer" />
+                </Tab>
+                <Tab text="Change stock" closable="false">
+                    <GridPane fx:id="stockTabContainer" />
+                </Tab>
+                <Tab text="Search" closable="false">
+                    <HBox fx:id="searchTabContainer" />
+                </Tab>
+                <Tab text="New fuel station" closable="false">
+                    <GridPane fx:id="newStationTabContainer" />
+                </Tab>
+                <Tab text="Overview" closable="false">
+                    <BorderPane fx:id="overviewTabContainer" />
+                </Tab>
+            </tabs>
+        </TabPane>
+    </center>
+
+    <!-- BOTTOM — output area -->
+    <bottom>
+        <VBox>
+            <Label text="Output:" />
+            <TextArea fx:id="outputArea" editable="false" wrapText="true" prefRowCount="5" />
+        </VBox>
+    </bottom>
+</BorderPane>
\ No newline at end of file
diff --git a/src/main/resources/ui/NewStationTab.fxml b/src/main/resources/ui/NewStationTab.fxml
new file mode 100644 (file)
index 0000000..6d91c64
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<GridPane xmlns:fx="http://javafx.com/fxml"
+       fx:controller="de.diejungsvondertanke.tankstelle.controllers.NewStationTabController"
+       hgap="10" vgap="10">
+       <padding>
+              <Insets top="15" right="15" bottom="15" left="15"/>
+       </padding>
+
+       <Label text="Type:" GridPane.rowIndex="0" GridPane.columnIndex="0" />
+
+       <ToggleGroup fx:id="group" />
+       <RadioButton fx:id="rbSmall" text="Small" selected="true"
+              toggleGroup="$group"
+              GridPane.rowIndex="1" GridPane.columnIndex="0" />
+       <RadioButton fx:id="rbMedium" text="Medium"
+              toggleGroup="$group"
+              GridPane.rowIndex="1" GridPane.columnIndex="1" />
+       <RadioButton fx:id="rbLarge" text="Large"
+              toggleGroup="$group"
+              GridPane.rowIndex="1" GridPane.columnIndex="2" />
+
+       <Label text="Attribute:" GridPane.rowIndex="2" GridPane.columnIndex="0" />
+       <TextField fx:id="txtAttr" GridPane.rowIndex="2" GridPane.columnIndex="1"
+              GridPane.columnSpan="2" />
+
+       <Label text="Hint:" GridPane.rowIndex="3" GridPane.columnIndex="0" />
+       <Label text="Small=vending machines, Medium=m², Large=supermarket-company"
+              wrapText="true"
+              GridPane.rowIndex="3" GridPane.columnIndex="1" GridPane.columnSpan="2" />
+
+       <Button text="Add fuel station" onAction="#add"
+              GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="3" />
+
+</GridPane>
\ No newline at end of file
diff --git a/src/main/resources/ui/OverviewTab.fxml b/src/main/resources/ui/OverviewTab.fxml
new file mode 100644 (file)
index 0000000..22f89d1
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<BorderPane xmlns:fx="http://javafx.com/fxml"
+    fx:controller="de.diejungsvondertanke.tankstelle.controllers.OverviewTabController"
+>
+    <padding>
+        <Insets top="15" right="15" bottom="15" left="15"/>
+    </padding>
+
+    <top>
+        <HBox spacing="10">
+            <Label text="Overview of all fuels" />
+            <Button text="Refresh" onAction="#refresh" />
+        </HBox>
+    </top>
+
+    <center>
+        <TableView fx:id="table">
+            <columns>
+                <TableColumn fx:id="colStation" text="Fuel Station" />
+                <TableColumn fx:id="colType" text="Station Type" />
+                <TableColumn fx:id="colFuel" text="Fuel" />
+                <TableColumn fx:id="colAmount" text="Amount (L)" />
+                <TableColumn fx:id="colCapacity" text="Capacity (L)" />
+                <TableColumn fx:id="colPrice" text="Price (€/L)" />
+            </columns>
+        </TableView>
+    </center>
+</BorderPane>
\ No newline at end of file
diff --git a/src/main/resources/ui/PriceTab.fxml b/src/main/resources/ui/PriceTab.fxml
new file mode 100644 (file)
index 0000000..c8acfdc
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.layout.*?>
+<?import javafx.scene.control.*?>
+
+<GridPane xmlns:fx="http://javafx.com/fxml"
+    fx:controller="de.diejungsvondertanke.tankstelle.controllers.PriceTabController"
+    hgap="10" vgap="10">
+    <padding>
+        <Insets top="15" right="15" bottom="15" left="15"/>
+    </padding>
+
+    <Label text="New price (€/L):" GridPane.rowIndex="0" GridPane.columnIndex="0" />
+    <TextField fx:id="txtPrice" GridPane.rowIndex="0" GridPane.columnIndex="1" />
+
+    <Button text="Change price" onAction="#save"
+        GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" />
+
+</GridPane>
\ No newline at end of file
diff --git a/src/main/resources/ui/ResultTab.fxml b/src/main/resources/ui/ResultTab.fxml
new file mode 100644 (file)
index 0000000..4720bc7
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.layout.*?>
+<?import javafx.scene.control.*?>
+
+<HBox xmlns:fx="http://javafx.com/fxml"
+    fx:controller="de.diejungsvondertanke.tankstelle.controllers.ResultTabController"
+    spacing="20">
+    <padding>
+        <Insets top="15" right="15" bottom="15" left="15"/>
+    </padding>
+
+    <VBox spacing="10">
+        <Button text="Total selling value of all fuel types" onAction="#handleTotalPrice" />
+        <Button text="Highest price (chosen fuel type)" onAction="#handleHighestPrice" />
+        <Button text="Highest total value" onAction="#handleHighestTotalValue" />
+        <Button text="Highest / lowest stock" onAction="#handleStock" />
+        <Button text="Total stock (selection)" onAction="#handleTotalStock" />
+    </VBox>
+
+    <VBox spacing="10">
+        <Label text="Fuel station selection (multiple):" />
+        <ListView fx:id="listFuelStations" prefWidth="220" />
+    </VBox>
+
+</HBox>
\ No newline at end of file
diff --git a/src/main/resources/ui/SearchTab.fxml b/src/main/resources/ui/SearchTab.fxml
new file mode 100644 (file)
index 0000000..5812cdd
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<HBox xmlns:fx="http://javafx.com/fxml"
+    fx:controller="de.diejungsvondertanke.tankstelle.controllers.SearchTabController"
+>
+    <padding>
+        <Insets top="15" right="15" bottom="15" left="15"/>
+    </padding>
+
+    <Button text="Show stations with chosen fuel type" onAction="#search" />
+
+</HBox>
\ No newline at end of file
diff --git a/src/main/resources/ui/StockTab.fxml b/src/main/resources/ui/StockTab.fxml
new file mode 100644 (file)
index 0000000..5f5501c
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+
+<GridPane xmlns:fx="http://javafx.com/fxml"
+      fx:controller="de.diejungsvondertanke.tankstelle.controllers.StockTabController"
+      hgap="10" vgap="10">
+      <padding>
+            <Insets top="15" right="15" bottom="15" left="15"/>
+      </padding>
+
+      <Label text="Amount (L):" GridPane.rowIndex="0" GridPane.columnIndex="0" />
+      <TextField fx:id="txtAmount" GridPane.rowIndex="0" GridPane.columnIndex="1" />
+
+      <RadioButton fx:id="rbAbsolute" text="Set absolute" selected="true"
+            GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" />
+      <RadioButton fx:id="rbDelta" text="Change (+/-)"
+            GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" />
+
+      <Button text="Change stock" onAction="#save"
+            GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" />
+</GridPane>
\ No newline at end of file