Merge branch 'dev' into feature_taskList_m2

This commit is contained in:
schrom01 2022-11-24 21:55:52 +01:00
commit d7b9095050
45 changed files with 1029 additions and 547 deletions

View File

@ -1,5 +1,6 @@
package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.models.Garden;
@ -10,13 +11,18 @@ import ch.zhaw.gartenverwaltung.types.Crop;
import ch.zhaw.gartenverwaltung.types.Pest;
import ch.zhaw.gartenverwaltung.types.Plant;
import ch.zhaw.gartenverwaltung.types.Task;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import java.io.IOException;
@ -34,7 +40,12 @@ public class CropDetailController {
@Inject
private Garden garden;
@Inject
AppLoader appLoader;
private static final Logger LOG = Logger.getLogger(CropDetailController.class.getName());
private final ListProperty<Task> taskListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
private final ListProperty<Pest> pestListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
@FXML
private ImageView imageView;
@ -51,9 +62,6 @@ public class CropDetailController {
@FXML
private Label description_label;
@FXML
private VBox growthPhases_vbox;
@FXML
private Label location_label;
@ -63,9 +71,6 @@ public class CropDetailController {
@FXML
private Button location_button;
@FXML
private VBox pests_vbox;
@FXML
private Label soil_label;
@ -73,26 +78,50 @@ public class CropDetailController {
private Label spacing_label;
@FXML
void editTaskList() {
private Button addTask_button;
@FXML
private ListView<Task> taskList_listView;
@FXML
private ListView<Pest> pests_listView;
@FXML
void addTask() throws IOException {
createTaskDialog(true, null);
}
/**
* close Window
*/
@FXML
void goBack() {
Stage stage = (Stage) imageView.getScene().getWindow();
stage.close();
}
/**
* open dialog to set area
*/
@FXML
void setArea() {
}
/**
* open dialog to set location
*/
@FXML
void setLocation() {
}
/**
* set labels and image from selected {@link Crop}
* set icons for buttons
* @param crop {@link Crop} which will be displayed
* @throws PlantNotFoundException exception
*/
public void setPlantFromCrop(Crop crop) throws PlantNotFoundException {
this.crop = crop;
try {
@ -109,22 +138,65 @@ public class CropDetailController {
}
area_label.setText("");
location_label.setText("");
createTaskLists(crop);
createPestList(plant);
setTaskListProperty(crop);
taskList_listView.itemsProperty().bind(taskListProperty);
pestListProperty.addAll(plant.pests());
pests_listView.itemsProperty().bind(pestListProperty);
} catch (HardinessZoneNotSetException | IOException e) {
throw new PlantNotFoundException();
}
setIconToButton(addTask_button, "addIcon.png");
setIconToButton(area_button, "areaIcon.png");
setIconToButton(location_button, "locationIcon.png");
setCellFactoryPests();
setCellFactoryTasks();
}
private void createTaskLists(Crop crop) {
private void setCellFactoryTasks() {
taskList_listView.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(Task task, boolean empty) {
super.updateItem(task, empty);
if (empty || task == null) {
setText(null);
setGraphic(null);
} else {
setText("");
setGraphic(createTaskHBox(task));
}
}
});
}
private void setCellFactoryPests() {
pests_listView.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(Pest pest, boolean empty) {
super.updateItem(pest, empty);
if (empty || pest == null) {
setText(null);
setGraphic(null);
} else {
setText("");
setGraphic(createPestHBox(pest));
}
}
});
}
private void setTaskListProperty(Crop crop) {
crop.getCropId().ifPresent(id -> {
List<Task> taskList;
try {
taskList = gardenSchedule.getTaskListForCrop(id);
for (Task task : taskList) {
Label label = new Label(task.getDescription());
growthPhases_vbox.getChildren().add(label);
}
taskListProperty.clear();
taskListProperty.addAll(taskList);
} catch (IOException e) {
// TODO: Alert
LOG.log(Level.SEVERE, "Could not get task list for crop", e.getCause());
@ -132,9 +204,26 @@ public class CropDetailController {
});
}
private void createPestList(Plant plant) {
List<Pest> pests = plant.pests();
for (Pest pest : pests) {
private HBox createTaskHBox(Task task) {
HBox hBox = new HBox();
Label taskName = new Label(task.getName()+": ");
taskName.setStyle("-fx-font-weight: bold");
Label taskDescription = new Label(task.getDescription());
taskDescription.setWrapText(true);
taskDescription.setMaxWidth(2000);
HBox.setHgrow(taskDescription, Priority.ALWAYS);
Button edit = new Button();
Button delete = new Button();
setIconToButton(edit, "editIcon.png");
setIconToButton(delete, "deleteIcon.png");
edit.setOnAction(getEditTaskEvent(task));
hBox.getChildren().addAll(taskName, taskDescription, edit, delete);
return hBox;
}
private HBox createPestHBox(Pest pest) {
Label label = new Label(pest.name() + ": ");
label.setStyle("-fx-font-weight: bold");
HBox hBox = new HBox();
@ -143,10 +232,72 @@ public class CropDetailController {
label1.setAlignment(Pos.TOP_LEFT);
label1.setWrapText(true);
label1.setMaxWidth(600);
label1.setMaxHeight(100);
Button button = new Button("Get Counter Measures");
hBox.getChildren().addAll(label1, button);
pests_vbox.getChildren().addAll(label, hBox);
}
hBox.getChildren().addAll(label, label1, button);
return hBox;
}
/**
* adds icon to button
* @param button the button which get the icon
* @param iconFileName file name of icon
*/
private void setIconToButton(Button button, String iconFileName) {
Image img = new Image(String.valueOf(getClass().getResource("icons/" + iconFileName)));
ImageView imageView = new ImageView(img);
imageView.setFitHeight(20);
imageView.setPreserveRatio(true);
button.setGraphic(imageView);
}
private EventHandler<ActionEvent> getEditTaskEvent(Task task) {
return (event) -> {
try {
createTaskDialog(false, task);
} catch (IOException e) {
e.printStackTrace();
}
};
}
private void createTaskDialog(boolean newTask, Task givenTask) throws IOException {
Dialog<Task> dialog = new Dialog<>();
dialog.setTitle("Set Task");
dialog.setHeaderText("Add/Edit Task:");
dialog.setResizable(false);
DialogPane dialogPane = dialog.getDialogPane();
ButtonType saveTask;
if(newTask) {
saveTask = new ButtonType("Add", ButtonBar.ButtonData.OK_DONE);
} else {
saveTask = new ButtonType("Save", ButtonBar.ButtonData.OK_DONE);
}
dialogPane.getButtonTypes().addAll(saveTask, ButtonType.CANCEL);
if (appLoader.loadPaneToDialog("TaskFormular.fxml", dialogPane) instanceof TaskFormularController controller) {
controller.setCorp(this.crop);
if (!newTask) {
controller.setTaskValue(givenTask);
}
dialog.setResultConverter(button -> button.equals(saveTask) ? controller.returnResult() : null);
dialog.showAndWait()
.ifPresent(task -> {
if (newTask) {
try {
gardenSchedule.addTask(task);
setTaskListProperty(this.crop);
} catch (IOException e) {
e.printStackTrace();
}
} else {
//ToDo method to edit task
setTaskListProperty(this.crop);
}
});
}
}
}

View File

@ -1,5 +1,47 @@
package ch.zhaw.gartenverwaltung;
public class HomeController
{
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import java.net.URL;
import java.util.ResourceBundle;
public class HomeController implements Initializable {
@FXML
private ImageView imageViewDavid;
@FXML
private ImageView imageViewElias;
@FXML
private ImageView imageViewGian;
@FXML
private ImageView imageViewPhilippe;
@FXML
private ImageView imageViewRoman;
@Override
public void initialize(URL location, ResourceBundle resources) {
setImages(imageViewDavid, "");
setImages(imageViewElias, "");
setImages(imageViewGian, "");
setImages(imageViewRoman, "");
setImages(imageViewPhilippe, "");
}
private void setImages(ImageView imageView, String photoName) {
Image img;
if (photoName.equals("")) {
img = new Image(String.valueOf(getClass().getResource("icons/userIcon.png")));
} else {
img = new Image(String.valueOf(getClass().getResource("icons/" + photoName)));
}
imageView.setImage(img);
}
}

View File

@ -6,7 +6,9 @@ import ch.zhaw.gartenverwaltung.bootstrap.ChangeViewEvent;
import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
@ -33,30 +35,71 @@ public class MainFXMLController {
private Button mySchedule_button;
@FXML
private Button plants_button;
private Button settings_button;
@FXML
private Button tutorial_button;
/**
* go to home pane
*/
@FXML
void goToHome() {
showPaneAsMainView("Home.fxml");
styleChangeButton(home_button);
}
/**
* go to my garden pane
*/
@FXML
void goToMyPlants() {
showPaneAsMainView("MyGarden.fxml");
styleChangeButton(myGarden_button);
}
/**
* go to the schedule pane
*/
@FXML
void goToMySchedule() {
showPaneAsMainView("MySchedule.fxml");
styleChangeButton(mySchedule_button);
}
/**
* open dialog of the settings
* @throws IOException exception
*/
@FXML
void goToPlants() {
showPaneAsMainView("Plants.fxml");
styleChangeButton(plants_button);
public void openSettings() throws IOException {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setTitle("Settings");
dialog.setHeaderText("Settings");
dialog.setResizable(false);
DialogPane dialogPane = dialog.getDialogPane();
ButtonType saveSettings = new ButtonType("Save", ButtonBar.ButtonData.OK_DONE);
dialogPane.getButtonTypes().addAll(saveSettings, ButtonType.CANCEL);
if (appLoader.loadPaneToDialog("Settings.fxml", dialogPane) instanceof SettingsController controller) {
dialog.showAndWait()
.ifPresent(button -> {
if (button.equals(saveSettings)) {
controller.saveSettings();
}
});
}
}
/**
* go to Tutorial pane
*/
public void goToTutorial() {
showPaneAsMainView("Tutorial.fxml");
styleChangeButton(tutorial_button);
}
/**
@ -81,10 +124,15 @@ public class MainFXMLController {
private final EventHandler<ChangeViewEvent> changeMainViewHandler = (ChangeViewEvent event) -> showPaneAsMainView(event.view());
/**
* preload all menu bar panes
* @throws IOException exception
*/
private void preloadPanes() throws IOException {
appLoader.loadAndCacheFxml("MyGarden.fxml");
appLoader.loadAndCacheFxml("MySchedule.fxml");
appLoader.loadAndCacheFxml("Plants.fxml");
appLoader.loadAndCacheFxml("Tutorial.fxml");
}
private void styleChangeButton(Button button) {
@ -100,11 +148,32 @@ public class MainFXMLController {
public void init() {
try {
preloadPanes();
showPaneAsMainView("Home.fxml");
styleChangeButton(home_button);
showPaneAsMainView("MyGarden.fxml");
styleChangeButton(myGarden_button);
} catch (IOException e) {
LOG.log(Level.SEVERE, "Failed to load FXML-Pane!", e);
}
}
setIconToButton(home_button, "homeIcon.png");
setIconToButton(settings_button, "settingsIcon.png");
Settings.getInstance().getShowTutorialProperty().addListener((observable, oldValue, newValue) -> {
tutorial_button.setVisible(newValue);
});
tutorial_button.setVisible(Settings.getInstance().getShowTutorial());
}
/**
* adds icon to given button
* @param button the button which get the icon
* @param iconFileName file name of icon
*/
private void setIconToButton(Button button, String iconFileName) {
Image img = new Image(String.valueOf(getClass().getResource("icons/" + iconFileName)));
ImageView imageView = new ImageView(img);
imageView.setFitHeight(20);
imageView.setPreserveRatio(true);
button.setGraphic(imageView);
}
}

View File

@ -13,15 +13,12 @@ import ch.zhaw.gartenverwaltung.types.Plant;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
@ -42,39 +39,64 @@ public class MyGardenController {
@FXML
public AnchorPane myGardenRoot;
@FXML
private VBox myPlants_vbox;
private Button addPlant_button;
@FXML
private ListView<Crop> myGarden_listView;
/**
* initialize crop list
* add listener for crop list
* set icon for button
*/
@AfterInject
@SuppressWarnings("unused")
public void init() {
garden.getPlantedCrops().addListener((observable, oldValue, newValue) -> {
try {
createPlantView(newValue);
} catch (HardinessZoneNotSetException | IOException e) {
LOG.log(Level.SEVERE, "Could not update view of croplist!", e);
}
});
try {
createPlantView(garden.getPlantedCrops());
} catch (HardinessZoneNotSetException | IOException e) {
LOG.log(Level.SEVERE, "Could not update view of croplist!", e);
}
System.out.println("once");
setIconToButton(addPlant_button, "addIcon.png");
myGarden_listView.itemsProperty().bind(garden.getPlantedCrops());
setCellFactory();
}
/**
* redirect to plant fxml file
*/
@FXML
void addPlant() {
myGardenRoot.fireEvent(new ChangeViewEvent(ChangeViewEvent.CHANGE_MAIN_VIEW, "Plants.fxml"));
}
private void createPlantView(List<Crop> crops) throws HardinessZoneNotSetException, IOException {
myPlants_vbox.getChildren().clear();
for (Crop crop : crops) {
HBox hBox = createPlantView(crop);
myPlants_vbox.getChildren().add(hBox);
/**
* set cell factory to load {@link HBox} as list view content
*/
private void setCellFactory() {
myGarden_listView.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(Crop crop, boolean empty) {
super.updateItem(crop, empty);
if (empty || crop == null) {
setText(null);
setGraphic(null);
} else {
try {
setText("");
setGraphic(createHBoxForListView(crop));
} catch (HardinessZoneNotSetException | IOException e) {
LOG.log(Level.WARNING, "Could not get plant for Cell", e);
}
}
}
});
}
private HBox createPlantView(Crop crop) throws HardinessZoneNotSetException, IOException {
/**
* Creates and returns HBox of the crop
* @param crop {@link Crop} which is selected
* @return {@link HBox} of the {@link Crop}
* @throws HardinessZoneNotSetException exception
* @throws IOException exception
*/
private HBox createHBoxForListView(Crop crop) throws HardinessZoneNotSetException, IOException {
//ToDo add better design
Plant plant = plantList.getPlantById(Settings.getInstance().getCurrentHardinessZone(), crop.getPlantId()).get();
HBox hBox = new HBox(10);
@ -91,8 +113,10 @@ public class MyGardenController {
label.setMaxWidth(2000);
HBox.setHgrow(label, Priority.ALWAYS);
Button details = new Button("Details");
Button delete = new Button("delete");
Button details = new Button();
Button delete = new Button();
setIconToButton(details, "detailsIcon.png");
setIconToButton(delete, "deleteIcon.png");
details.setOnAction(getGoToCropDetailEvent(crop));
delete.setOnAction(getDeleteCropEvent(crop));
@ -100,6 +124,24 @@ public class MyGardenController {
return hBox;
}
/**
* adds icon to button
* @param button the button which get the icon
* @param iconFileName file name of icon
*/
private void setIconToButton(Button button, String iconFileName) {
Image img = new Image(String.valueOf(getClass().getResource("icons/" + iconFileName)));
ImageView imageView = new ImageView(img);
imageView.setFitHeight(20);
imageView.setPreserveRatio(true);
button.setGraphic(imageView);
}
/**
* open detail window of the selected {@link Crop}
* @param crop {@link Crop} which is selected
* @return {@link EventHandler} for button
*/
private EventHandler<ActionEvent> getGoToCropDetailEvent(Crop crop) {
return (event) -> {
try {
@ -117,6 +159,11 @@ public class MyGardenController {
};
}
/**
* open alert for deleting the selected {@link Crop}
* @param crop {@link Crop} which is selected
* @return {@link EventHandler} for button
*/
private EventHandler<ActionEvent> getDeleteCropEvent(Crop crop) {
return (event) -> {
try {
@ -127,9 +174,16 @@ public class MyGardenController {
};
}
/**
* Alert to confirm that the crop can be deleted.
* @param crop {@link Crop} which is selected
* @throws IOException exception
* @throws HardinessZoneNotSetException exception
*/
private void showConfirmation(Crop crop) throws IOException, HardinessZoneNotSetException {
Plant plant = plantList.getPlantById(Settings.getInstance().getCurrentHardinessZone(), crop.getPlantId()).get();
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Delete Crop");
alert.setTitle("Delete " + plant.name());
alert.setHeaderText("Are you sure want to delete this Crop?");
alert.setContentText("Deleting this crop will remove all associated tasks from your schedule.");

View File

@ -41,7 +41,6 @@ public class PlantsController {
private Plant selectedPlant = null;
private final HardinessZone DEFAULT_HARDINESS_ZONE = HardinessZone.ZONE_8A;
// TODO: move to model
private final ListProperty<Plant> plantListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
@FXML
@ -269,7 +268,7 @@ public class PlantsController {
list_plants.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
selectedPlant = newValue;
description_plant.setText(selectedPlant.description());
description_plant.setText(getPlantDescription());
selectSowDay_button.setDisable(false);
Image img1;
if (selectedPlant.image() != null) {
@ -288,6 +287,20 @@ public class PlantsController {
});
}
/**
* creates {@link String} of the plant information.
* @return return {@link Plant} description
*/
private String getPlantDescription() {
StringBuilder sb = new StringBuilder();
sb.append("Name: ").append(selectedPlant.name())
.append("\nDescription:\n").append(selectedPlant.description())
.append("\nLight Level: ").append(selectedPlant.light())
.append("\nSoil: ").append(selectedPlant.soil())
.append("\nSpacing: ").append(selectedPlant.spacing());
return sb.toString();
}
/**
* clears the ListView of entries

View File

@ -21,6 +21,11 @@ public class SelectSowDayController {
@FXML
public ToggleGroup phase_group;
/**
* if sow date radio button was selected return sow date
* if sow date was not selected get sow from harvest day and return sow date
* @return {@link LocalDate} of the sow date
*/
public LocalDate retrieveResult() {
LocalDate sowDate = datepicker.getValue();
if (harvest_radio.isSelected()) {
@ -53,6 +58,10 @@ public class SelectSowDayController {
harvest_radio.setUserData(GrowthPhaseType.HARVEST);
}
/**
* Disable save button when date picker is empty
* @param saveButton {@link Button} to be disabled
*/
public void initSaveButton(Button saveButton) {
saveButton.disableProperty().bind(datepicker.valueProperty().isNull());
}

View File

@ -1,10 +1,13 @@
package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
public class Settings {
private HardinessZone currentHardinessZone = HardinessZone.ZONE_8A;
private static Settings instance;
private final BooleanProperty showTutorial = new SimpleBooleanProperty(false);
static {
instance = new Settings();
@ -23,4 +26,16 @@ public class Settings {
public void setCurrentHardinessZone(HardinessZone currentHardinessZone) {
this.currentHardinessZone = currentHardinessZone;
}
public void setShowTutorial (boolean showTutorial) {
this.showTutorial.setValue(showTutorial);
}
public BooleanProperty getShowTutorialProperty() {
return this.showTutorial;
}
public boolean getShowTutorial() {
return this.showTutorial.get();
}
}

View File

@ -0,0 +1,40 @@
package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import java.net.URL;
import java.util.ResourceBundle;
public class SettingsController implements Initializable {
Settings settings = Settings.getInstance();
@FXML
private ComboBox<HardinessZone> selectHardinessZone_comboBox;
@FXML
private CheckBox showTutorial_checkBox;
/**
* save selected values to {@link Settings}
*/
public void saveSettings() {
settings.setShowTutorial(showTutorial_checkBox.isSelected());
settings.setCurrentHardinessZone(selectHardinessZone_comboBox.getValue());
}
/**
* save default values form {@link Settings}
* @param location location
* @param resources resources
*/
@Override
public void initialize(URL location, ResourceBundle resources) {
showTutorial_checkBox.setSelected(settings.getShowTutorial());
selectHardinessZone_comboBox.getItems().addAll(HardinessZone.values());
selectHardinessZone_comboBox.setValue(settings.getCurrentHardinessZone());
}
}

View File

@ -0,0 +1,86 @@
package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.types.Crop;
import ch.zhaw.gartenverwaltung.types.Task;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.DateCell;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.util.Callback;
import java.net.URL;
import java.time.LocalDate;
import java.util.ResourceBundle;
public class TaskFormularController implements Initializable {
private Crop crop;
@FXML
private TextArea description_area;
@FXML
private DatePicker end_datePicker;
@FXML
private TextField interval_field;
@FXML
private DatePicker start_datePicker;
@FXML
private TextField taskName_field;
public Task returnResult() {
Task task = new Task(taskName_field.getText(), description_area.getText(),
start_datePicker.getValue(), end_datePicker.getValue(),
Integer.parseInt(interval_field.getText()), crop.getCropId().get());
return task;
}
public void setCorp(Crop crop) {
this.crop = crop;
}
public void setTaskValue(Task task) {
taskName_field.setText(task.getName());
description_area.setText(task.getDescription());
start_datePicker.setValue(task.getStartDate());
end_datePicker.setValue(task.getEndDate().orElse(null));
if(task.getInterval().orElse(null)!=null) {
interval_field.setText(task.getInterval().get().toString());
} else {
interval_field.setText(null);
}
}
private Callback<DatePicker, DateCell> getDayCellFactory() {
return (datePicker) -> new DateCell() {
private final LocalDate today = LocalDate.now();
@Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
setDisable(true);
setStyle("-fx-background-color: #ffc0cb;");
if (item.compareTo(today) > 0 && item.compareTo(crop.getStartDate()) > 0) {
setDisable(false);
setStyle("-fx-background-color: #32CD32;");
}
}
};
}
@Override
public void initialize(URL location, ResourceBundle resources) {
start_datePicker.setDayCellFactory(getDayCellFactory());
start_datePicker.setEditable(false);
end_datePicker.setDayCellFactory(getDayCellFactory());
end_datePicker.setEditable(false);
}
}

View File

@ -0,0 +1,6 @@
package ch.zhaw.gartenverwaltung;
public class TutorialController {
}

View File

@ -53,6 +53,7 @@ public class JsonCropList implements CropList {
public JsonCropList(URL dataSource) {
this.dataSource = dataSource;
}
/**
* {@inheritDoc}
*/

View File

@ -25,7 +25,7 @@ import java.util.Optional;
* The reads are cached to minimize file-io operations.
*/
public class JsonPlantList implements PlantList {
private final URL dataSource = getClass().getResource("plantdb.json");
private final URL dataSource;
private HardinessZone currentZone;
private Map<Long, Plant> plantMap = Collections.emptyMap();
@ -44,6 +44,13 @@ public class JsonPlantList implements PlantList {
imageModule.addDeserializer(Image.class, new PlantImageDeserializer());
}
public JsonPlantList() {
this.dataSource = getClass().getResource("plantdb.json");
}
public JsonPlantList(URL dataSource) {
this.dataSource = dataSource;
}
/**
* If no data is currently loaded, or the specified zone differs
* from the {@link #currentZone}, data is loaded from {@link #dataSource}.

View File

@ -25,7 +25,7 @@ import java.util.Map;
*/
public class JsonTaskList implements TaskList {
IdProvider idProvider;
private final URL dataSource = getClass().getResource("taskdb.json");
private final URL dataSource;
private final static String INVALID_DATASOURCE_MSG = "Invalid datasource specified!";
private Map<Long, Task> taskMap = Collections.emptyMap();
@ -43,6 +43,13 @@ public class JsonTaskList implements TaskList {
timeModule.addSerializer(LocalDate.class, dateSerializer);
}
public JsonTaskList() {
this.dataSource = getClass().getResource("taskdb.json");
}
public JsonTaskList(URL dataSource) {
this.dataSource = dataSource;
}
/**
* If no data is currently loaded, data is loaded from {@link #dataSource}.
* In any case, the values of {@link #taskMap} are returned.
@ -96,9 +103,9 @@ public class JsonTaskList implements TaskList {
if(taskMap.isEmpty()) {
loadTaskListFromFile();
}
if(task.getId() == 0) {
task.withId(idProvider.incrementAndGet());
}
long id = task.getId().orElse(idProvider.incrementAndGet());
taskMap.put(id, task.withId(id));
writeTaskListToFile();
}
@ -114,8 +121,9 @@ public class JsonTaskList implements TaskList {
if(taskMap.isEmpty()) {
loadTaskListFromFile();
}
if(taskMap.containsKey(task.getId())){
taskMap.remove(task.getId());
Long taskId = task.getId().orElseThrow(IOException::new);
if(taskMap.containsKey(taskId)){
taskMap.remove(taskId);
writeTaskListToFile();
}
}
@ -155,7 +163,7 @@ public class JsonTaskList implements TaskList {
taskMap = result.stream()
.collect(HashMap::new,
(res, task) -> res.put(task.getId(), task),
(res, task) -> res.put(task.getId().orElse(0L), task),
(existing, replacement) -> {});
}

View File

@ -9,7 +9,7 @@ import java.util.Optional;
* May be created using the builder pattern.
*/
public class Task {
private long id;
private Long id;
private final String name;
private final String description;
private final LocalDate startDate;
@ -62,7 +62,7 @@ public class Task {
}
// Getters
public long getId() { return id; }
public Optional<Long> getId() { return Optional.ofNullable(id); }
public String getName() { return name; }
public String getDescription() { return description; }
public LocalDate getStartDate() { return startDate; }

View File

@ -34,6 +34,7 @@ public class TaskTemplate {
public void setRelativeEndDate(Integer relativeEndDate) {
this.relativeEndDate = relativeEndDate;
}
public void setInterval(Integer interval) {
this.interval = interval;
}
@ -45,13 +46,10 @@ public class TaskTemplate {
}
public Task generateTask(LocalDate realStartDate, long cropId) {
Task task = new Task(name, description, realStartDate.plusDays(relativeStartDate), cropId);
if (relativeEndDate != null) {
task.withEndDate(realStartDate.plusDays(relativeEndDate));
}
if (interval != null) {
task.withInterval(interval);
}
return task;
LocalDate endDate = relativeEndDate != null ? realStartDate.plusDays(relativeEndDate) : null;
return new Task(name, description, realStartDate.plusDays(relativeStartDate), cropId)
.withInterval(interval)
.withEndDate(endDate);
}
}

View File

@ -3,6 +3,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
@ -12,13 +13,11 @@
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="785.0" prefWidth="899.0"
xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.CropDetailController">
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="785.0" prefWidth="899.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.CropDetailController">
<children>
<ScrollPane fitToWidth="true" prefHeight="759.0" prefWidth="664.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<content>
<VBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="503.0" prefWidth="897.0">
<VBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="781.0" prefWidth="897.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
@ -80,27 +79,27 @@
<ImageView fx:id="imageView" fitHeight="300.0" fitWidth="300.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER" />
</children>
</HBox>
<Label text="Growth Phases:">
<Label text="Tasks:">
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<VBox fx:id="growthPhases_vbox" prefHeight="135.0" prefWidth="879.0">
<ListView fx:id="taskList_listView" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="877.0" VBox.vgrow="ALWAYS">
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</VBox>
<Button mnemonicParsing="false" onAction="#editTaskList" prefHeight="25.0" prefWidth="92.0" text="Edit Tasklist">
</ListView>
<Button fx:id="addTask_button" mnemonicParsing="false" onAction="#addTask" prefHeight="25.0" prefWidth="45.0">
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Button>
<Label text="Pests:" />
<VBox fx:id="pests_vbox" prefHeight="200.0" prefWidth="100.0">
<ListView fx:id="pests_listView" maxHeight="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</VBox>
</ListView>
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" VBox.vgrow="NEVER">
<children>
<Label text="Area:">
@ -113,7 +112,7 @@
<Insets right="10.0" />
</HBox.margin>
</Label>
<Button fx:id="area_button" mnemonicParsing="false" onAction="#setArea" prefHeight="25.0" prefWidth="116.0" text="Add Area" />
<Button fx:id="area_button" mnemonicParsing="false" onAction="#setArea" prefHeight="25.0" prefWidth="116.0" text="Set Area" />
</children>
<VBox.margin>
<Insets bottom="10.0" />
@ -131,7 +130,7 @@
<Insets right="10.0" />
</HBox.margin>
</Label>
<Button fx:id="location_button" mnemonicParsing="false" onAction="#setLocation" prefHeight="25.0" prefWidth="115.0" text="Add Location" />
<Button fx:id="location_button" mnemonicParsing="false" onAction="#setLocation" prefHeight="25.0" prefWidth="115.0" text="Set Location" />
</children>
</HBox>
</children>

View File

@ -2,42 +2,210 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="729.0" prefWidth="1060.0"
xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.HomeController">
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1091.0" prefWidth="1060.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.HomeController">
<children>
<VBox layoutX="75.0" layoutY="73.0" prefHeight="729.0" prefWidth="1060.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<ScrollPane fitToWidth="true" prefHeight="1157.0" prefWidth="1060.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<content>
<VBox prefHeight="1091.0" prefWidth="1058.0">
<children>
<Label text="Gartenverwaltung">
<Pane prefHeight="1085.0" prefWidth="1018.0">
<children>
<VBox prefHeight="1047.0" prefWidth="1019.0">
<children>
<Label text="Garden Management">
<font>
<Font size="34.0" />
</font>
<VBox.margin>
<Insets bottom="30.0" />
</VBox.margin>
</Label>
<Label prefHeight="106.0" prefWidth="1040.0" text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." wrapText="true" />
<Label text="Tutorial">
<Label alignment="TOP_LEFT" prefHeight="22.0" prefWidth="1039.0" text="This Application was created to help the user manage his or her garden. For this the Application has many functionalities:" wrapText="true">
<font>
<Font name="System Bold" size="18.0" />
<Font size="14.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<Label text="Base Functionalities:">
<font>
<Font name="System Bold" size="14.0" />
</font>
</Label>
<Pane prefHeight="200.0" prefWidth="200.0">
<Label text="- The user can select a plant he wants to cultivate.">
<font>
<Font size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="62.0" text="- The user can filter the plants according to seasons, hardiness zone and search query">
<font>
<Font size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="62.0" text="- The user can select the harverst or sow date. ">
<font>
<Font size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="102.0" text="- The user can get a detailed information of the plant he wants to harvest.">
<font>
<Font size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="122.0" text="- The user can get view the task list of the given plant.">
<font>
<Font size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="142.0" text="- The user can get the tasks of the next seven days in the scheduler.">
<font>
<Font size="14.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<Label layoutX="10.0" layoutY="42.0" text="Advanced Functionalities:">
<font>
<Font name="System Bold" size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="62.0" text="- The user can edit the task list and add custom tasks.">
<font>
<Font size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="212.0" text="- The user can set the area (sqare meter) for the plants.">
<font>
<Font size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="232.0" text="- The user can set the location (PLZ) for the plants.">
<font>
<Font size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="162.0" text="- The user can set the pesticide which will be used, which will create additonal tasks.">
<font>
<Font size="14.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<Label layoutX="10.0" layoutY="192.0" text="Weather Forcast:">
<font>
<Font name="System Bold" size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="212.0" text="- According to the location the weather forcast will crate or delete tasks.">
<font>
<Font size="14.0" />
</font>
</Label>
<Label layoutX="10.0" layoutY="272.0" text="- The user receives notifications that aditional tasks werde created or some tasks were deleted.">
<font>
<Font size="14.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<Label text="Created by:">
<font>
<Font name="System Bold" size="14.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0">
<children>
<VBox prefHeight="200.0" prefWidth="1040.0">
<ImageView fx:id="imageViewElias" fitHeight="100.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER">
<HBox.margin>
<Insets right="30.0" />
</HBox.margin>
</ImageView>
<Label text="Elias Csomor" />
</children>
<VBox.margin>
<Insets bottom="15.0" />
</VBox.margin>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0">
<children>
<Label text="Task 1" wrapText="true" />
<Label text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." wrapText="true" />
<ImageView fx:id="imageViewPhilippe" fitHeight="100.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER">
<HBox.margin>
<Insets right="30.0" />
</HBox.margin>
</ImageView>
<Label text="Philippe Giavarini" />
</children>
<VBox.margin>
<Insets bottom="15.0" />
</VBox.margin>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0">
<children>
<ImageView fx:id="imageViewDavid" fitHeight="100.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER">
<HBox.margin>
<Insets right="30.0" />
</HBox.margin>
</ImageView>
<Label text="David Guler" />
</children>
<VBox.margin>
<Insets bottom="15.0" />
</VBox.margin>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0">
<children>
<ImageView fx:id="imageViewGian" fitHeight="100.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER">
<HBox.margin>
<Insets right="30.0" />
</HBox.margin>
</ImageView>
<Label text="Gian-Andrea Hutter" />
</children>
<VBox.margin>
<Insets bottom="15.0" />
</VBox.margin>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0">
<children>
<ImageView fx:id="imageViewRoman" fitHeight="100.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER">
<HBox.margin>
<Insets right="30.0" />
</HBox.margin>
</ImageView>
<Label text="Roman Schenk" />
</children>
<VBox.margin>
<Insets bottom="15.0" />
</VBox.margin>
</HBox>
</children>
</VBox>
</children>
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</VBox.margin>
</Pane>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</VBox>
</content>
</ScrollPane>
</children>
</AnchorPane>

View File

@ -9,11 +9,12 @@
<children>
<HBox maxWidth="35.0" minHeight="35.0" prefHeight="38.0" prefWidth="1110.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button fx:id="home_button" mnemonicParsing="false" onAction="#goToHome" prefHeight="38.0" prefWidth="121.0" text="Home" HBox.hgrow="NEVER" />
<Button fx:id="plants_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToPlants" prefHeight="38.0" prefWidth="121.0" text="Plants" />
<Button fx:id="home_button" mnemonicParsing="false" onAction="#goToHome" prefHeight="38.0" prefWidth="40.0" HBox.hgrow="NEVER" />
<Button fx:id="myGarden_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToMyPlants" prefHeight="38.0" prefWidth="121.0" text="My Garden" />
<Button fx:id="mySchedule_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToMySchedule" prefHeight="38.0" prefWidth="121.0" text="My Schedule" />
<Button fx:id="tutorial_button" layoutX="10.0" layoutY="10.0" mnemonicParsing="false" onAction="#goToTutorial" prefHeight="38.0" prefWidth="121.0" text="Tutorial" />
<Pane maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
<Button fx:id="settings_button" maxHeight="1.7976931348623157E308" mnemonicParsing="false" onAction="#openSettings" prefHeight="38.0" prefWidth="40.0" HBox.hgrow="NEVER" />
</children>
</HBox>
<AnchorPane fx:id="mainPane" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="35.0" />

View File

@ -3,11 +3,12 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="655.0" prefWidth="1175.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.MyGardenController" fx:id="myGardenRoot">
<AnchorPane fx:id="myGardenRoot" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="655.0" prefWidth="1175.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.MyGardenController">
<children>
<VBox layoutY="49.0" prefHeight="655.0" prefWidth="1175.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
@ -19,7 +20,7 @@
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<VBox fx:id="myPlants_vbox" maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="100.0" VBox.vgrow="ALWAYS" />
<ListView fx:id="myGarden_listView" maxHeight="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS" />
<Button fx:id="addPlant_button" mnemonicParsing="false" onAction="#addPlant" prefHeight="45.0" prefWidth="155.0" text="Add new Plant">
<VBox.margin>
<Insets top="10.0" />

View File

@ -63,7 +63,7 @@
<Font name="System Bold" size="17.0" />
</font>
</Label>
<TitledPane animated="false" text="Saison">
<TitledPane animated="false" text="Seasons">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="80.0" prefWidth="374.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.SettingsController">
<children>
<VBox layoutX="14.0" layoutY="14.0" prefHeight="73.0" prefWidth="374.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<HBox prefHeight="23.0" prefWidth="334.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="Show Tutorial Menu" HBox.hgrow="ALWAYS" />
<CheckBox fx:id="showTutorial_checkBox" mnemonicParsing="false" />
</children>
<VBox.margin>
<Insets left="10.0" right="10.0" top="10.0" />
</VBox.margin>
</HBox>
<HBox alignment="CENTER_LEFT" layoutX="20.0" layoutY="20.0" prefHeight="23.0" prefWidth="334.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="Select Default Hardiness Zone" HBox.hgrow="ALWAYS" />
<ComboBox fx:id="selectHardinessZone_comboBox" prefWidth="150.0" promptText="Hardniness Zone" />
</children>
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</VBox.margin>
</HBox>
</children>
</VBox>
</children>
</AnchorPane>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="259.0" prefWidth="390.0" xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.TaskFormularController">
<children>
<VBox layoutX="14.0" layoutY="14.0" prefHeight="272.0" prefWidth="390.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<children>
<HBox alignment="CENTER_LEFT" prefHeight="35.0" prefWidth="560.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="Task name:" HBox.hgrow="ALWAYS" />
<TextField fx:id="taskName_field" promptText="Task Name" />
</children>
</HBox>
<HBox prefHeight="77.0" prefWidth="350.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="Description:" HBox.hgrow="ALWAYS" />
<TextArea fx:id="description_area" prefHeight="73.0" prefWidth="206.0" promptText="Description" />
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutX="30.0" layoutY="30.0" prefHeight="35.0" prefWidth="560.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="Start Date:" HBox.hgrow="ALWAYS" />
<DatePicker fx:id="start_datePicker" />
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutX="30.0" layoutY="143.0" prefHeight="35.0" prefWidth="560.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="End Date:" HBox.hgrow="ALWAYS" />
<DatePicker fx:id="end_datePicker" />
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutX="30.0" layoutY="30.0" prefHeight="35.0" prefWidth="560.0">
<children>
<Label maxWidth="1.7976931348623157E308" text="Interval:" HBox.hgrow="ALWAYS" />
<TextField fx:id="interval_field" promptText="Interval (e.g. 0, 1, 3 ...)" />
</children>
</HBox>
</children>
</VBox>
</children>
</AnchorPane>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.TutorialController">
<children>
<VBox layoutX="7.0" layoutY="8.0" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label text="Tutorial">
<font>
<Font size="18.0" />
</font>
</Label>
<Label text="To be added" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</VBox>
</children>
</AnchorPane>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -184,15 +184,14 @@
"litersPerSqM": 15,
"interval": 4,
"notes": [
]
},
"taskTemplates": [
{
"name": "hilling",
"name": "Plant Sets",
"relativeStartDate": 0,
"relativeEndDate": 0,
"description": "Mound up the soil around the plant until just the top few leaves show above the soil. ",
"description": "Plant the sets about 5cm deep into the soil.",
"interval": null,
"isOptional": false
}
@ -232,7 +231,6 @@
"litersPerSqM": 0,
"interval": null,
"notes": [
]
},
"taskTemplates": [

View File

@ -28,8 +28,8 @@ public class JsonCropListTest {
/**
* Files to isolate the test-units
*/
private final URL dbDataSource = this.getClass().getResource("user-crops.json");
private final URL testFile = this.getClass().getResource("test-user-crops.json");
private final URL dbDataSource = this.getClass().getResource("test-user-crops.json");
private final URL testFile = this.getClass().getResource("template-user-crops.json");
@BeforeEach
void connectToDb() throws URISyntaxException, IOException {

View File

@ -8,17 +8,22 @@ import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class JsonPlantListTest {
private final URL testFile = this.getClass().getResource("test-plantdb.json");
PlantList testDatabase;
@BeforeEach
void connectToDb() {
testDatabase = new JsonPlantList();
assertNotNull(testFile);
testDatabase = new JsonPlantList(testFile);
}

View File

@ -18,27 +18,27 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
public class JsonTaskListTest {
TaskList testDatabase;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final URL dbDataSource = this.getClass().getResource("taskdb.json");
private final URL testFile = this.getClass().getResource("test-taskdb.json");
private final URL dbDataSource = this.getClass().getResource("test-taskdb.json");
private final URL testFile = this.getClass().getResource("template-taskdb.json");
@BeforeEach
void connectToDb() throws URISyntaxException, IOException {
assertNotNull(testFile);
assertNotNull(dbDataSource);
Files.copy(Path.of(testFile.toURI()), Path.of(dbDataSource.toURI()), StandardCopyOption.REPLACE_EXISTING);
testDatabase = new JsonTaskList();
testDatabase = new JsonTaskList(dbDataSource);
}
@Test
@DisplayName("Check if results are retrieved completely")
void getTasks() {
List<Task> taskList = null;
List<Task> taskList;
try {
taskList = testDatabase.getTaskList(LocalDate.parse("30.04.2022", formatter),
LocalDate.parse("31.05.2022", formatter));
taskList = testDatabase.getTaskList(LocalDate.parse("2022-04-30", formatter),
LocalDate.parse("2022-05-31", formatter));
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -54,7 +54,7 @@ public class JsonTaskListTest {
Task task = new Task("Testtask", "This is a test Task.", LocalDate.parse("01.05.2022", formatter), 1);
try {
testDatabase.saveTask(task);
List<Task> taskList = null;
List<Task> taskList;
try {
taskList = testDatabase.getTaskList(LocalDate.parse("30.04.2022", formatter),
LocalDate.parse("31.05.2022", formatter));
@ -72,13 +72,14 @@ public class JsonTaskListTest {
@Test
@DisplayName("Remove task.")
void removeTask() {
Task task = new Task("Dummy", "Dummy", LocalDate.parse("31.05.2022", formatter), 1).withId(2);
Task task = new Task("Dummy", "Dummy", LocalDate.parse("2022-05-31", formatter), 1)
.withId(2);
try {
testDatabase.removeTask(task);
List<Task> taskList = null;
List<Task> taskList;
taskList = testDatabase.getTaskList(LocalDate.parse("30.04.2022", formatter),
LocalDate.parse("31.05.2022", formatter));
taskList = testDatabase.getTaskList(LocalDate.parse("2022-04-30", formatter),
LocalDate.parse("2022-05-31", formatter));
Assertions.assertEquals(2, taskList.size());
} catch (IOException e) {
@ -90,7 +91,7 @@ public class JsonTaskListTest {
@Test
void getTaskForCrop() {
List<Task> taskList = null;
List<Task> taskList;
try {
taskList = testDatabase.getTaskForCrop(0);
} catch (IOException e) {
@ -104,7 +105,7 @@ public class JsonTaskListTest {
@Disabled("Disabled until removing works")
@Test
void removeTasksForCrop() {
List<Task> taskList = null;
List<Task> taskList;
try {
testDatabase.removeTasksForCrop(0);
taskList = testDatabase.getTaskForCrop(0);

View File

@ -22,8 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.*;
public class GardenPlanModelTest {
private final URL dbDataSource = JsonCropList.class.getResource("user-crops.json");
private final URL testFile = JsonCropList.class.getResource("test-user-crops.json");
private final URL dbDataSource = JsonCropListTest.class.getResource("test-user-crops.json");
private final URL testFile = JsonCropListTest.class.getResource("template-user-crops.json");
CropList cropList;
List<Crop> exampleCrops;
@ -55,8 +55,8 @@ public class GardenPlanModelTest {
new GrowthPhase(MonthDay.of(2, 8), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>()),
new GrowthPhase(MonthDay.of(10, 2), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>())));
exampleCropOnion = new Crop(examplePlantOnion.id(), LocalDate.of(2023,3,1));
exampleCropOnion.withId(3);
exampleCropOnion = new Crop(examplePlantOnion.id(), LocalDate.of(2023, 3, 1))
.withId(3);
examplePlantCarrot = new Plant(
1,
"Early Carrot",
@ -67,18 +67,18 @@ public class GardenPlanModelTest {
"sandy to loamy, loose soil, free of stones",
new ArrayList<>(),
List.of(new GrowthPhase(MonthDay.of(4, 4), MonthDay.of(12, 4), 0, new WateringCycle(0, 0, null), GrowthPhaseType.PLANT, HardinessZone.ZONE_8A, new ArrayList<>())));
exampleCropCarrot = new Crop(examplePlantCarrot.id(), LocalDate.now());
exampleCropCarrot.withId(5);
exampleCropCarrot = new Crop(examplePlantCarrot.id(), LocalDate.now())
.withId(5);
exampleCrop1 = new Crop(1, LocalDate.of(2023,2,25));
exampleCrop1.withId(0);
exampleCrop1.withArea(0.5);
exampleCrop2 = new Crop(1,LocalDate.of(2023,3,1));
exampleCrop2.withId(1);
exampleCrop2.withArea(0.5);
exampleCrop3 = new Crop(0,LocalDate.of(2023,3,1));
exampleCrop3.withId(2);
exampleCrop3.withArea(1.0);
exampleCrop1 = new Crop(1, LocalDate.of(2023, 2, 25))
.withId(0)
.withArea(0.5);
exampleCrop2 = new Crop(1, LocalDate.of(2023, 3, 1))
.withId(1)
.withArea(0.5);
exampleCrop3 = new Crop(0, LocalDate.of(2023, 3, 1))
.withId(2)
.withArea(1.0);
exampleCrops = new ArrayList<>();
exampleCrops.add(exampleCrop1);

View File

@ -1,257 +0,0 @@
[
{
"id": 0,
"name": "Potato",
"description": "The potato is a tuber, round or oval, with small white roots called 'eyes', that are growth buds. The size varies depending on the variety; the colour of the skin can be white, yellow or even purple.",
"light": 6,
"spacing": "35",
"soil": "sandy",
"image": "potato.jpg",
"pests": [
{
"name": "Rot",
"description": "Rot, any of several plant diseases, caused by any of hundreds of species of soil-borne bacteria, fungi, and funguslike organisms (Oomycota). Rot diseases are characterized by plant decomposition and putrefaction. The decay may be hard, dry, spongy, watery, mushy, or slimy and may affect any plant part.",
"measures": "Less water."
}
],
"lifecycle": [
{
"startDate": "03-10",
"endDate": "04-10",
"type": "SOW",
"zone": "ZONE_8A",
"group": 0,
"wateringCycle": {
"litersPerSqM": 0,
"interval": null,
"notes": []
},
"taskTemplates": [
{
"name": "Germinate",
"relativeStartDate": -14,
"relativeEndDate": null,
"description": "\"Take an egg carton and fill it with soil. Put the seedling deep enaugh so its half covered with soil. Keep it in 10-15 * Celsius with lots of light.\"",
"interval": null,
"isOptional": false
}
]
},
{
"startDate": "04-10",
"endDate": "07-10",
"type": "PLANT",
"zone": "ZONE_8A",
"group": 0,
"wateringCycle": {
"litersPerSqM": 25,
"interval": 7,
"notes": []
},
"taskTemplates": [
{
"name": "hilling",
"relativeStartDate": 0,
"relativeEndDate": null,
"description": "\"When the plants are 20 cm tall, begin hilling the potatoes by gently mounding the soil from the center of your rows around the stems of the plant. Mound up the soil around the plant until just the top few leaves show above the soil. Two weeks later, hill up the soil again when the plants grow another 20 cm.\"",
"interval": 21,
"isOptional": false
}
]
},
{
"startDate": "06-10",
"endDate": "08-10",
"type": "HARVEST",
"zone": "ZONE_8A",
"group": 0,
"wateringCycle": {
"litersPerSqM": 0,
"interval": null,
"notes": []
},
"taskTemplates": [
{
"name": "Harvest",
"relativeStartDate": 0,
"relativeEndDate": null,
"description": "Once the foliage has wilted and dried completely, harvest on a dry day. Store in a dark and cool location.",
"interval": null,
"isOptional": false
}
]
}
]
},
{
"id": 1,
"name": "Early Carrot",
"description": "Carrot, (Daucus carota), herbaceous, generally biennial plant of the Apiaceae family that produces an edible taproot. Among common varieties root shapes range from globular to long, with lower ends blunt to pointed. Besides the orange-coloured roots, white-, yellow-, and purple-fleshed varieties are known.",
"image": "carrot.jpg",
"lifecycle": [
{
"startDate": "02-20",
"endDate": "03-10",
"zone": "ZONE_8A",
"type": "SOW",
"wateringCycle": {
"litersPerSqM": 15,
"interval": 3,
"notes": []
},
"taskTemplates": [
{
"name": "hilling",
"relativeStartDate": 0,
"relativeEndDate": 0,
"description": "Mound up the soil around the plant until just the top few leaves show above the soil. ",
"interval": null,
"isOptional": false
}
]
},
{
"startDate": "03-10",
"endDate": "05-10",
"zone": "ZONE_8A",
"type": "PLANT",
"wateringCycle": {
"litersPerSqM": 25,
"interval": 3,
"notes": [
"Be careful not to pour water over the leaves, as this will lead to sunburn."
]
},
"taskTemplates": [
{
"name": "hilling",
"relativeStartDate": 0,
"relativeEndDate": null,
"description": "Mound up the soil around the plant until just the top few leaves show above the soil. ",
"interval": 15,
"isOptional": true
}
]
},
{
"startDate": "05-10",
"endDate": "05-20",
"zone": "ZONE_8A",
"type": "HARVEST",
"wateringCycle": {
"litersPerSqM": 0,
"interval": null,
"notes": []
},
"taskTemplates": [
{
"name": "Harvesting",
"relativeStartDate": 0,
"relativeEndDate": 14,
"description": "When the leaves turn to a yellowish brown. Do not harvest earlier. The plant will show when it's ready.",
"interval": null,
"isOptional": false
}
]
}
],
"soil": "sandy to loamy, loose soil, free of stones",
"spacing": "5,35,2.5",
"pests": [
{
"name": "Rot",
"description": "rot, any of several plant diseases, caused by any of hundreds of species of soil-borne bacteria, fungi, and funguslike organisms (Oomycota). Rot diseases are characterized by plant decomposition and putrefaction. The decay may be hard, dry, spongy, watery, mushy, or slimy and may affect any plant part.",
"measures": "less water"
}
]
},
{
"id": 2,
"name": "Summertime Onion",
"description": "Onion, (Allium cepa), herbaceous biennial plant in the amaryllis family (Amaryllidaceae) grown for its edible bulb. The onion is likely native to southwestern Asia but is now grown throughout the world, chiefly in the temperate zones. Onions are low in nutrients but are valued for their flavour and are used widely in cooking. They add flavour to such dishes as stews, roasts, soups, and salads and are also served as a cooked vegetable.",
"image": "onion.jpg",
"lifecycle": [
{
"startDate": "03-15",
"endDate": "04-10",
"type": "SOW",
"zone": "ZONE_8A",
"group": 0,
"wateringCycle": {
"litersPerSqM": 15,
"interval": 4,
"notes": [
]
},
"taskTemplates": [
{
"name": "hilling",
"relativeStartDate": 0,
"relativeEndDate": 0,
"description": "Mound up the soil around the plant until just the top few leaves show above the soil. ",
"interval": null,
"isOptional": false
}
]
},
{
"startDate": "04-10",
"endDate": "07-10",
"type": "PLANT",
"zone": "ZONE_8A",
"group": 0,
"wateringCycle": {
"litersPerSqM": 25,
"interval": 3,
"notes": [
""
]
},
"taskTemplates": [
{
"name": "hilling",
"relativeStartDate": 0,
"relativeEndDate": null,
"description": "Mound up the soil around the plant until just the top few leaves show above the soil. ",
"interval": 15,
"isOptional": true
}
]
},
{
"startDate": "07-10",
"endDate": "09-20",
"type": "HARVEST",
"zone": "ZONE_8A",
"group": 0,
"wateringCycle": {
"litersPerSqM": 0,
"interval": null,
"notes": [
]
},
"taskTemplates": [
{
"name": "Harvesting",
"relativeStartDate": 0,
"relativeEndDate": 14,
"description": "When ready for harvest, the leaves on your onion plants will start to flop over. This happens at the \"neck\" of the onion and it signals that the plant has stopped growing and is ready for storage. Onions should be harvested soon thereafter",
"interval": null,
"isOptional": false
}
]
}
],
"soil": "sandy to loamy, loose soil, free of stones",
"spacing": "15,30,2",
"pests": [
{
"name": "Rot",
"description": "rot, any of several plant diseases, caused by any of hundreds of species of soil-borne bacteria, fungi, and funguslike organisms (Oomycota). Rot diseases are characterized by plant decomposition and putrefaction. The decay may be hard, dry, spongy, watery, mushy, or slimy and may affect any plant part.",
"measures": "less water"
}
]
}
]

View File

@ -0,0 +1,20 @@
[
{
"cropId": 0,
"plantId": 1,
"startDate": "2023-02-25",
"area": 0.5
},
{
"cropId": 1,
"plantId": 1,
"startDate": "2023-03-01",
"area": 0.5
},
{
"cropId": 2,
"plantId": 0,
"startDate": "2023-03-25",
"area": 1.5
}
]

View File

@ -31,7 +31,7 @@
"name": "Germinate",
"relativeStartDate": -14,
"relativeEndDate": null,
"description": "\"Take an egg carton and fill it with soil. Put the seedling deep enaugh so its half covered with soil. Keep it in 10-15 * Celsius with lots of light.\"",
"description": "Take an egg carton and fill it with soil. Put the seedling deep enough so its half covered with soil. Keep it in 10-15 * Celsius with lots of light.",
"interval": null,
"isOptional": false
}
@ -53,7 +53,7 @@
"name": "hilling",
"relativeStartDate": 0,
"relativeEndDate": null,
"description": "\"When the plants are 20 cm tall, begin hilling the potatoes by gently mounding the soil from the center of your rows around the stems of the plant. Mound up the soil around the plant until just the top few leaves show above the soil. Two weeks later, hill up the soil again when the plants grow another 20 cm.\"",
"description": "When the plants are 20 cm tall, begin hilling the potatoes by gently mounding the soil from the center of your rows around the stems of the plant. Mound up the soil around the plant until just the top few leaves show above the soil. Two weeks later, hill up the soil again when the plants grow another 20 cm.",
"interval": 21,
"isOptional": false
}
@ -94,6 +94,7 @@
"endDate": "03-10",
"zone": "ZONE_8A",
"type": "SOW",
"group": 0,
"wateringCycle": {
"litersPerSqM": 15,
"interval": 3,
@ -115,6 +116,7 @@
"endDate": "05-10",
"zone": "ZONE_8A",
"type": "PLANT",
"group": 0,
"wateringCycle": {
"litersPerSqM": 25,
"interval": 3,
@ -138,6 +140,7 @@
"endDate": "05-20",
"zone": "ZONE_8A",
"type": "HARVEST",
"group": 0,
"wateringCycle": {
"litersPerSqM": 0,
"interval": null,

View File

@ -1,56 +1,2 @@
[
{
"id" : 1,
"name" : "sow plant",
"description": "Plant the seeds, crops in de bed.",
"startDate" : "2022-05-01",
"endDate" : "2022-05-01",
"interval" : 0,
"cropId" : 0
},
{
"id" : 2,
"name" : "water plant",
"description": "water the plant, so that the soil is wet around the plant.",
"startDate" : "2022-05-01",
"endDate" : "2022-09-01",
"interval" : 2,
"cropId" : 0
},
{
"id" : 3,
"name" : "fertilize plant",
"description": "The fertilizer has to be mixed with water. Then fertilize the plants soil with the mixture",
"startDate" : "2022-06-01",
"endDate" : "2022-08-01",
"interval" : 28,
"cropId" : 0
},
{
"id" : 4,
"name" : "covering plant",
"description": "Take a big enough coverage for the plants. Cover the whole plant with a bit space between the plant and the coverage",
"startDate" : "2022-07-01",
"endDate" : "2022-07-01",
"interval" : 0,
"cropId" : 0
},
{
"id" : 5,
"name" : "look after plant",
"description": "Look for pest or illness at the leaves of the plant. Check the soil around the plant, if the roots are enough covered with soil",
"startDate" : "2022-05-01",
"endDate" : "2022-09-01",
"interval" : 5,
"cropId" : 0
},
{
"id" : 6,
"name" : "harvest plant",
"description": "Pull the ripe vegetables out from the soil. Clean them with clear, fresh water. ",
"startDate" : "2022-09-01",
"endDate" : "2022-09-01",
"interval" : 0,
"cropId" : 0
}
]

View File

@ -1,20 +1,2 @@
[
{
"cropId": 0,
"plantId": 1,
"startDate": "2023-02-25",
"area": 0.5
},
{
"cropId": 1,
"plantId": 1,
"startDate": "2023-03-01",
"area": 0.5
},
{
"cropId": 2,
"plantId": 0,
"startDate": "2023-03-25",
"area": 1.5
}
]