diff --git a/src/main/java/de/hitec/nhplus/fixtures/MedicationFixture.java b/src/main/java/de/hitec/nhplus/fixtures/MedicationFixture.java index fed7e20..8dc9f96 100644 --- a/src/main/java/de/hitec/nhplus/fixtures/MedicationFixture.java +++ b/src/main/java/de/hitec/nhplus/fixtures/MedicationFixture.java @@ -67,7 +67,6 @@ public class MedicationFixture implements Fixture { Ingredient warfarinnatrium = new Ingredient("Warfarinnatrium"); medications.add(new Medication( - 1, "Metformin", "AstraZeneca", List.of( @@ -81,7 +80,6 @@ public class MedicationFixture implements Fixture { 100 )); medications.add(new Medication( - 2, "Lisinopril", "Teva Pharmaceuticals", List.of( @@ -95,7 +93,6 @@ public class MedicationFixture implements Fixture { 150 )); medications.add(new Medication( - 3, "Simvastatin", "Mylan", List.of( @@ -109,7 +106,6 @@ public class MedicationFixture implements Fixture { 80 )); medications.add(new Medication( - 4, "Enoxaparin", "Sanofi", List.of( @@ -122,7 +118,6 @@ public class MedicationFixture implements Fixture { 120 )); medications.add(new Medication( - 5, "Levothyroxin", "Sandoz", List.of( @@ -136,7 +131,6 @@ public class MedicationFixture implements Fixture { 90 )); medications.add(new Medication( - 6, "Warfarin", "Apotex Inc.", List.of( diff --git a/src/main/java/de/hitec/nhplus/medication/AllMedicationController.java b/src/main/java/de/hitec/nhplus/medication/AllMedicationController.java index 5261958..1370ff0 100644 --- a/src/main/java/de/hitec/nhplus/medication/AllMedicationController.java +++ b/src/main/java/de/hitec/nhplus/medication/AllMedicationController.java @@ -1,22 +1,30 @@ package de.hitec.nhplus.medication; +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import java.util.stream.Collectors; + +import de.hitec.nhplus.Main; import de.hitec.nhplus.datastorage.DaoFactory; import de.hitec.nhplus.medication.database.MedicationDao; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; - -import java.sql.SQLException; -import java.util.stream.Collectors; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; /** * The controller for viewing all {@link Medication}s. * * @author Dominik Säume + * @author Ole Kück */ public class AllMedicationController { @FXML @@ -50,14 +58,20 @@ public class AllMedicationController { this.columnName.setCellValueFactory(new PropertyValueFactory<>("name")); this.columnManufacturer.setCellValueFactory(new PropertyValueFactory<>("manufacturer")); this.columnIngredient.setCellValueFactory( - cellData -> new SimpleStringProperty( - cellData - .getValue() - .getIngredients() - .stream() - .map(ingredient -> ingredient.getName()) - .collect(Collectors.joining("\n")) - )); + cellData -> { + Medication medication = cellData.getValue(); + List ingredients = medication.getIngredients(); + if(ingredients.isEmpty()){ + return new SimpleStringProperty(""); + } + + return new SimpleStringProperty( + ingredients + .stream() + .map(ingredient -> ingredient.getName()) + .collect(Collectors.joining("\n")) + ); + }); this.columnPossibleSideEffects.setCellValueFactory(new PropertyValueFactory<>("possibleSideEffects")); this.columnAdministrationMethod.setCellValueFactory(new PropertyValueFactory<>("administrationMethod")); this.columnCurrentStock.setCellValueFactory(new PropertyValueFactory<>("currentStock")); @@ -76,4 +90,39 @@ public class AllMedicationController { exception.printStackTrace(); } } + + /** + * Method to create a new {@link Medication}. + */ + public void createMedication(Medication medication) { + dao = DaoFactory.getInstance().createMedicationDAO(); + try { + dao.create(medication); + } catch (SQLException exception) { + exception.printStackTrace(); + } + } + + @FXML + public void handleNewMedication() { + try { + FXMLLoader loader = new FXMLLoader( + Main.class.getResource("/de/hitec/nhplus/medication/MedicationModal.fxml") + ); + BorderPane pane = loader.load(); + Scene scene = new Scene(pane); + Stage stage = new Stage(); + + MedicationModalController controller = loader.getController(); + controller.initialize(stage, this, null); + + stage.setScene(scene); + stage.setTitle("NHPlus - Neues Medikament"); + stage.setResizable(true); + stage.setAlwaysOnTop(true); + stage.showAndWait(); + } catch (IOException exception) { + exception.printStackTrace(); + } + } } diff --git a/src/main/java/de/hitec/nhplus/medication/Ingredient.java b/src/main/java/de/hitec/nhplus/medication/Ingredient.java index bbf2f36..49f93f6 100644 --- a/src/main/java/de/hitec/nhplus/medication/Ingredient.java +++ b/src/main/java/de/hitec/nhplus/medication/Ingredient.java @@ -17,7 +17,7 @@ public class Ingredient { } public String getName() { - return name.get(); + return name.getValue(); } public SimpleStringProperty nameProperty() { diff --git a/src/main/java/de/hitec/nhplus/medication/IngredientListCell.java b/src/main/java/de/hitec/nhplus/medication/IngredientListCell.java new file mode 100644 index 0000000..5156e4d --- /dev/null +++ b/src/main/java/de/hitec/nhplus/medication/IngredientListCell.java @@ -0,0 +1,104 @@ +package de.hitec.nhplus.medication; + +import javafx.beans.value.ObservableValue; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.TextField; +import javafx.scene.layout.BorderPane; +import javafx.scene.text.Text; + +/** + * A custom implementation of the {@link ListCell} for {@link Ingredient}s. + * This implementation contains an automatic resizing of the parent {@link ListView}. + * + * @author Dominik Säume + */ +public class IngredientListCell extends ListCell { + private final TextField textField; + private final Button deleteButton; + + private static final double BUILTIN_BORDER_WIDTH = 1; + private static final double CELL_SPACING = 4; + private static final double CELL_PADDING = 4; + private static final double BUTTON_PADDING_X = 8; + private static final double BUTTON_PADDING_Y = 4; + private final double totalSpacing; + + public IngredientListCell() { + this.setPadding(new Insets(CELL_PADDING)); + + textField = new TextField(); + textField.setPromptText("Inhaltsstoff"); + textField.textProperty().addListener(this::onTextFieldUpdate); + + deleteButton = new Button("-"); + deleteButton.setPadding(new Insets( + BUTTON_PADDING_Y, + BUTTON_PADDING_X, + BUTTON_PADDING_Y, + BUTTON_PADDING_X + )); + deleteButton.setOnAction(event -> getListView().getItems().remove(this)); + + // Calculate Delete Button Width + Text textNode = new Text(deleteButton.getText()); + textNode.setFont(deleteButton.getFont()); + double calculatedDeleteButtonWidth = textNode.getLayoutBounds().getWidth() + BUTTON_PADDING_X * 2; + + totalSpacing = BUILTIN_BORDER_WIDTH * 2 // List View + + CELL_PADDING * 2 + + CELL_SPACING + + calculatedDeleteButtonWidth; + } + + @Override + protected void updateItem(Ingredient item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setGraphic(null); + } else { + String oldText = textField.getText(); + String newText = oldText != null && !oldText.isEmpty() ? oldText : item.getName(); + textField.setText(newText); + + BorderPane cellPane = new BorderPane(); + cellPane.setCenter(textField); + cellPane.setRight(deleteButton); + BorderPane.setMargin(deleteButton, new Insets(0, 0, 0, CELL_SPACING)); + setGraphic(cellPane); + } + } + + /** + * A Callback for use as a Listener for the {@link IngredientListCell#textField}. + */ + private void onTextFieldUpdate(ObservableValue observable, String oldValue, String newValue) { + getItem().setName(textField.getText()); + + textField.setMinWidth(getTextFieldRequiredWidth(textField)); + + ListView listView = getListView(); + double max = listView.lookupAll("*") + .stream() + .filter(node -> node instanceof IngredientListCell) + .mapToDouble(node -> getTextFieldRequiredWidth(((IngredientListCell) node).textField)) + .max() + .orElse(0); + + getListView().setMinWidth(max + totalSpacing); + } + + /** + * Internal method to calculate the required width of the {@link IngredientListCell#textField}. + */ + private double getTextFieldRequiredWidth(TextField textField) { + Text textNode = new Text(textField.getText()); + textNode.setFont(textField.getFont()); + return textNode.getLayoutBounds().getWidth() + + textField.getPadding().getLeft() + + textField.getPadding().getRight() + + BUILTIN_BORDER_WIDTH * 2; + } +} diff --git a/src/main/java/de/hitec/nhplus/medication/MedicationModalController.java b/src/main/java/de/hitec/nhplus/medication/MedicationModalController.java new file mode 100644 index 0000000..76a97c3 --- /dev/null +++ b/src/main/java/de/hitec/nhplus/medication/MedicationModalController.java @@ -0,0 +1,134 @@ +package de.hitec.nhplus.medication; + +import de.hitec.nhplus.treatment.Treatment; +import javafx.beans.value.ChangeListener; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ListView; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.stage.Stage; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import static de.hitec.nhplus.utils.Validator.*; + +/** + * The controller for creating and editing a specific {@link Medication}. + * + * @author Ole Kück + */ +public class MedicationModalController { + + @FXML + public ListView listViewIngredients; + @FXML + public TextField textFieldName; + @FXML + public TextField textFieldManufacturer; + @FXML + public TextField textFieldAdministrationMethod; + @FXML + public TextField textFieldCurrentStock; + @FXML + public TextArea textAreaPossibleSideEffects; + @FXML + public Button buttonSave; + + private Stage stage; + private Medication medication; + private final ObservableList ingredients = FXCollections.observableArrayList(); + private AllMedicationController controller; + + /** + * Initialization method that is called after the binding of all the fields. + */ + @FXML + public void initialize( + Stage stage, + AllMedicationController controller, + Medication medication + ) { + this.stage = stage; + this.controller=controller; + + if( medication != null){ + this.medication = medication; + }else { + this.medication = new Medication( + "", + "", + new ArrayList<>(), + "", + "", + 0 + ); + this.buttonSave.setDisable(true); + } + + listViewIngredients.setCellFactory(cellData -> new IngredientListCell()); + listViewIngredients.setItems(ingredients); + showData(); + + ChangeListener inputMedicationValidationListener = (observableValue, oldText, newText) -> { + boolean isValid = isValidMedicationName(textFieldName.getText()) + && isValidMedicationManufacturer(textFieldManufacturer.getText()) + && isValidMedicationAdministrationMethod(textFieldAdministrationMethod.getText()) + && isValidStock(textFieldCurrentStock.getText()); + + this.buttonSave.setDisable(!isValid); + }; + + this.textFieldName.textProperty().addListener(inputMedicationValidationListener); + this.textFieldManufacturer.textProperty().addListener(inputMedicationValidationListener); + this.textFieldAdministrationMethod.textProperty().addListener(inputMedicationValidationListener); + this.textFieldCurrentStock.textProperty().addListener(inputMedicationValidationListener); + } + + /** + * Internal method to show the data in the view. + */ + private void showData(){ + ingredients.setAll(medication.getIngredients()); + textFieldName.setText(medication.getName()); + textFieldManufacturer.setText(medication.getManufacturer()); + textFieldAdministrationMethod.setText(medication.getAdministrationMethod()); + textFieldCurrentStock.setText(String.valueOf(medication.getCurrentStock())); + textAreaPossibleSideEffects.setText(medication.getPossibleSideEffects()); + } + + @FXML + public void handleSave() { + this.medication.setName(textFieldName.getText()); + this.medication.setManufacturer(textFieldManufacturer.getText()); + this.medication.setAdministrationMethod(textFieldAdministrationMethod.getText()); + this.medication.setCurrentStock(Integer.parseInt(textFieldCurrentStock.getText())); + this.medication.setPossibleSideEffects(textAreaPossibleSideEffects.getText()); + this.medication.setIngredients(ingredients + .stream() + .map(Ingredient::getName) + .distinct() + .filter(Predicate.not(String::isEmpty)) + .map(Ingredient::new) + .toList() + ); + + controller.createMedication(medication); + controller.readAllAndShowInTableView(); + stage.close(); + } + + @FXML + public void handleCancel() { + stage.close(); + } + + @FXML + public void handleAddIngredient() { + ingredients.add(new Ingredient("")); + } +} diff --git a/src/main/java/de/hitec/nhplus/medication/database/MedicationDao.java b/src/main/java/de/hitec/nhplus/medication/database/MedicationDao.java index 556a623..e226770 100644 --- a/src/main/java/de/hitec/nhplus/medication/database/MedicationDao.java +++ b/src/main/java/de/hitec/nhplus/medication/database/MedicationDao.java @@ -29,6 +29,7 @@ public class MedicationDao implements Dao { @Override public void create(Medication medication) throws SQLException { + connection.setAutoCommit(false); //Switch to Manual Commit, to do an SQL Transaction final String medicationSQL = """ INSERT INTO medication (name, manufacturer, possibleSideEffects, administrationMethod, currentStock) @@ -42,6 +43,15 @@ public class MedicationDao implements Dao { medicationStatement.setInt(5, medication.getCurrentStock()); medicationStatement.execute(); + ResultSet generatedKeys = connection.createStatement().executeQuery("SELECT last_insert_rowid()"); + connection.commit(); //Finish SQL Transaction + connection.setAutoCommit(true); //Switch back Mode + + if (!generatedKeys.next()) { + return; + } + int newId = generatedKeys.getInt(1); + final String ingredientSQL = """ INSERT INTO medication_ingredient (id, name) @@ -49,7 +59,7 @@ public class MedicationDao implements Dao { """; for (Ingredient ingredient : medication.getIngredients()) { PreparedStatement ingredientStatement = this.connection.prepareStatement(ingredientSQL); - ingredientStatement.setInt(1, medication.getId()); + ingredientStatement.setInt(1, newId); ingredientStatement.setString(2, ingredient.getName()); ingredientStatement.execute(); } @@ -90,20 +100,26 @@ public class MedicationDao implements Dao { result.getInt(1), result.getString(2), result.getString(3), - List.of(), + new ArrayList<>(), result.getString(4), result.getString(5), result.getInt(6) ); medications.add(medication); - lastMedicationId = currentMedicationId; - continue; } List ingredients = ingredientMap.computeIfAbsent(currentMedicationId, k -> new ArrayList<>()); - ingredients.add(new Ingredient(result.getString(7))); + String ingredientName = result.getString(7); + if(ingredientName == null){ + continue; + } + ingredients.add(new Ingredient(ingredientName)); lastMedicationId = currentMedicationId; } for (Medication medication : medications) { + List ingredients = ingredientMap.get(medication.getId()); + if(ingredients.isEmpty()){ + continue; + } medication.setIngredients(ingredientMap.get(medication.getId())); } diff --git a/src/main/java/de/hitec/nhplus/utils/Validator.java b/src/main/java/de/hitec/nhplus/utils/Validator.java index 7931700..443fc3e 100644 --- a/src/main/java/de/hitec/nhplus/utils/Validator.java +++ b/src/main/java/de/hitec/nhplus/utils/Validator.java @@ -148,4 +148,46 @@ public class Validator { public static boolean isValidRoomNumber(String text) { return !text.isBlank(); } + + /** + * Validate that a {@link String} is a valid {@link de.hitec.nhplus.medication.Medication#name Medication name}. + * @param text The {@link String} to validate. + */ + public static boolean isValidMedicationName(String text) { + return !text.isBlank(); + } + + /** + * Validate that a {@link String} is a valid + * {@link de.hitec.nhplus.medication.Medication#manufacturer Medication manufacturer}. + * @param text The {@link String} to validate. + */ + public static boolean isValidMedicationManufacturer(String text) { + return !text.isBlank(); + } + + /** + * Validate that a {@link String} is a valid + * {@link de.hitec.nhplus.medication.Medication#administrationMethod Medication administration method}. + * @param text The {@link String} to validate. + */ + public static boolean isValidMedicationAdministrationMethod(String text) { + return !text.isBlank(); + } + + /** + * Validate that a {@link String} is a valid stock count. + * @param text The {@link String} to validate. + */ + public static boolean isValidStock(String text) { + if (text.isBlank()) { + return false; + } + try { + int stock = Integer.parseInt(text); + return stock >= 0; + } catch (Exception exception) { + return false; + } + } } diff --git a/src/main/resources/de/hitec/nhplus/medication/AllMedicationView.fxml b/src/main/resources/de/hitec/nhplus/medication/AllMedicationView.fxml index f5ee43b..53e03b9 100644 --- a/src/main/resources/de/hitec/nhplus/medication/AllMedicationView.fxml +++ b/src/main/resources/de/hitec/nhplus/medication/AllMedicationView.fxml @@ -66,6 +66,7 @@ fx:id="buttonAdd" mnemonicParsing="false" prefWidth="90.0" + onAction="#handleNewMedication" text="Hinzufügen" />