added dropdowns for breed desktop

This commit is contained in:
Alex
2026-04-13 21:40:34 -06:00
parent 3ef6604884
commit 98584f1324
7 changed files with 166 additions and 25 deletions

View File

@@ -42,6 +42,15 @@ public class DropdownApi {
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {}); return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
} }
public List<DropdownOption> getPetBreeds(String species) throws Exception {
String encoded = java.net.URLEncoder.encode(species, java.nio.charset.StandardCharsets.UTF_8);
String response = apiClient.getRawResponse("/api/v1/dropdowns/pet-breeds?species=" + encoded);
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from pet breeds endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getProducts() throws Exception { public List<DropdownOption> getProducts() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/products"); String response = apiClient.getRawResponse("/api/v1/dropdowns/products");
if (response == null || response.isEmpty()) { if (response == null || response.isEmpty()) {

View File

@@ -62,7 +62,7 @@ public class AdoptionDialogController {
private boolean suppressPaymentDialog = false; private boolean suppressPaymentDialog = false;
private ObservableList<String> statusList = FXCollections.observableArrayList( private ObservableList<String> statusList = FXCollections.observableArrayList(
"Pending", "Completed", "Cancelled" "Pending", "Completed", "Missed", "Cancelled"
); );
@FXML @FXML
@@ -282,6 +282,7 @@ public class AdoptionDialogController {
} }
suppressPaymentDialog = true; suppressPaymentDialog = true;
cbAdoptionStatus.setItems(statusList);
for (String status : cbAdoptionStatus.getItems()) { for (String status : cbAdoptionStatus.getItems()) {
if (status.equals(adoption.getAdoptionStatus())) { if (status.equals(adoption.getAdoptionStatus())) {
cbAdoptionStatus.getSelectionModel().select(status); cbAdoptionStatus.getSelectionModel().select(status);
@@ -289,13 +290,57 @@ public class AdoptionDialogController {
} }
} }
suppressPaymentDialog = false; suppressPaymentDialog = false;
applyEditModeLock();
} }
} }
private void applyEditModeLock() {
String status = cbAdoptionStatus.getValue();
if ("Cancelled".equalsIgnoreCase(status)) {
cbPet.setDisable(true);
cbCustomer.setDisable(true);
cbEmployee.setDisable(true);
dpAdoptionDate.setDisable(true);
cbAdoptionStatus.setDisable(true);
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Cancelled"));
btnSave.setDisable(true);
return;
}
LocalDate adoptionDate = dpAdoptionDate.getValue();
boolean isPast = adoptionDate != null && adoptionDate.isBefore(LocalDate.now());
cbPet.setDisable(true);
cbCustomer.setDisable(true);
dpAdoptionDate.setDisable(false);
cbEmployee.setDisable(false);
cbAdoptionStatus.setDisable(false);
suppressPaymentDialog = true;
if (isPast) {
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Completed", "Missed"));
dpAdoptionDate.setDisable(true);
} else {
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Pending", "Cancelled"));
}
if (!cbAdoptionStatus.getItems().contains(cbAdoptionStatus.getValue())) {
cbAdoptionStatus.getSelectionModel().selectFirst();
}
suppressPaymentDialog = false;
}
public void setMode(String mode) { public void setMode(String mode) {
this.mode = mode; this.mode = mode;
lblMode.setText(mode + " Adoption"); lblMode.setText(mode + " Adoption");
lblAdoptionId.setVisible(mode.equals("Edit")); lblAdoptionId.setVisible(mode.equals("Edit"));
if (mode.equals("Add")) {
suppressPaymentDialog = true;
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Pending"));
cbAdoptionStatus.setValue("Pending");
cbAdoptionStatus.setDisable(true);
suppressPaymentDialog = false;
}
} }
private void applySelectedPet() { private void applySelectedPet() {

View File

@@ -43,24 +43,26 @@ public class AppointmentDialogController {
@FXML private Label lblAppointmentId; @FXML private Label lblAppointmentId;
@FXML private Label lblMode; @FXML private Label lblMode;
private String mode = null; private String mode = null;
private AppointmentDTO selectedAppointment = null; private AppointmentDTO selectedAppointment = null;
private Long pendingPetSelectionId = null; private Long pendingPetSelectionId = null;
private boolean isOriginallyCancel = false;
private ObservableList<String> statusList = private boolean isOriginallyCompletedOrMissed = false;
FXCollections.observableArrayList(
"Booked", "Completed", "Missed", "Cancelled"
);
public void setMode(String mode) { public void setMode(String mode) {
this.mode = mode; this.mode = mode;
lblMode.setText(mode + " Appointment"); lblMode.setText(mode + " Appointment");
lblAppointmentId.setVisible(!mode.equals("Add")); lblAppointmentId.setVisible(!mode.equals("Add"));
if (mode.equals("Add")) {
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Booked"));
cbAppointmentStatus.setValue("Booked");
cbAppointmentStatus.setDisable(true);
}
} }
@FXML @FXML
public void initialize() { public void initialize() {
cbAppointmentStatus.setItems(statusList); cbAppointmentStatus.setItems(FXCollections.observableArrayList("Booked", "Completed", "Missed", "Cancelled"));
cbPet.setDisable(true); cbPet.setDisable(true);
cbEmployee.setPromptText("Select an employee"); cbEmployee.setPromptText("Select an employee");
cbPet.setPromptText("Select a customer first"); cbPet.setPromptText("Select a customer first");
@@ -228,6 +230,46 @@ public class AppointmentDialogController {
applySelectedService(); applySelectedService();
applySelectedCustomer(); applySelectedCustomer();
applySelectedEmployee(); applySelectedEmployee();
applyEditModeLock();
}
private void applyEditModeLock() {
String status = cbAppointmentStatus.getValue();
isOriginallyCancel = "Cancelled".equalsIgnoreCase(status);
isOriginallyCompletedOrMissed = "Completed".equalsIgnoreCase(status) || "Missed".equalsIgnoreCase(status);
if (isOriginallyCancel) {
cbService.setDisable(true);
cbCustomer.setDisable(true);
cbPet.setDisable(true);
cbEmployee.setDisable(true);
cbHour.setDisable(true);
cbMinute.setDisable(true);
dpAppointmentDate.setDisable(true);
cbAppointmentStatus.setDisable(true);
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Cancelled"));
btnSave.setDisable(true);
} else if (isOriginallyCompletedOrMissed) {
cbService.setDisable(true);
cbCustomer.setDisable(true);
cbPet.setDisable(true);
cbEmployee.setDisable(true);
cbHour.setDisable(true);
cbMinute.setDisable(true);
dpAppointmentDate.setDisable(true);
cbAppointmentStatus.setDisable(false);
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Completed", "Missed"));
} else {
cbService.setDisable(true);
cbCustomer.setDisable(true);
cbPet.setDisable(true);
cbEmployee.setDisable(false);
cbHour.setDisable(false);
cbMinute.setDisable(false);
dpAppointmentDate.setDisable(false);
cbAppointmentStatus.setDisable(false);
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Booked", "Cancelled"));
}
} }
private void buttonSaveClicked(MouseEvent e) { private void buttonSaveClicked(MouseEvent e) {

View File

@@ -121,7 +121,7 @@ public class InventoryDialogController {
//Validate inputs //Validate inputs
errorMsg += Validator.isPresent(txtQuantity.getText(), "Quantity"); errorMsg += Validator.isPresent(txtQuantity.getText(), "Quantity");
errorMsg += Validator.isLessThanVarChars(txtQuantity.getText(), "Quantity", 11); errorMsg += Validator.isLessThanVarChars(txtQuantity.getText(), "Quantity", 11);
errorMsg += Validator.isNonNegativeInteger(txtQuantity.getText(), "Quantity"); errorMsg += Validator.isPositiveInteger(txtQuantity.getText(), "Quantity");
//Operation only occurs if there are no errors //Operation only occurs if there are no errors
if (errorMsg.isEmpty()) { if (errorMsg.isEmpty()) {

View File

@@ -28,6 +28,7 @@ import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
public class PetDialogController { public class PetDialogController {
@@ -41,6 +42,8 @@ public class PetDialogController {
@FXML private Button btnChangeImage; @FXML private Button btnChangeImage;
@FXML private Button btnRemoveImage; @FXML private Button btnRemoveImage;
@FXML private ComboBox<String> cbPetStatus; @FXML private ComboBox<String> cbPetStatus;
@FXML private ComboBox<String> cbPetSpecies;
@FXML private ComboBox<String> cbPetBreed;
@FXML private ComboBox<DropdownOption> cbCustomer; @FXML private ComboBox<DropdownOption> cbCustomer;
@FXML private ComboBox<DropdownOption> cbStore; @FXML private ComboBox<DropdownOption> cbStore;
@FXML private VBox vbCustomerField; @FXML private VBox vbCustomerField;
@@ -51,10 +54,8 @@ public class PetDialogController {
@FXML private Label lblImageStatus; @FXML private Label lblImageStatus;
@FXML private ImageView imgPetPreview; @FXML private ImageView imgPetPreview;
@FXML private TextField txtPetAge; @FXML private TextField txtPetAge;
@FXML private TextField txtPetBreed;
@FXML private TextField txtPetName; @FXML private TextField txtPetName;
@FXML private TextField txtPetPrice; @FXML private TextField txtPetPrice;
@FXML private ComboBox<String> cbPetSpecies;
private String mode = null; private String mode = null;
private File selectedImageFile; private File selectedImageFile;
@@ -65,6 +66,7 @@ public class PetDialogController {
private Long pendingStoreId = null; private Long pendingStoreId = null;
private Long originalCustomerId = null; private Long originalCustomerId = null;
private boolean isOriginallyOwnedOrAdopted = false; private boolean isOriginallyOwnedOrAdopted = false;
private String pendingBreedValue = null;
private final ObservableList<String> statusList = FXCollections.observableArrayList( private final ObservableList<String> statusList = FXCollections.observableArrayList(
STATUS_AVAILABLE, STATUS_ADOPTED, STATUS_OWNED, STATUS_PENDING STATUS_AVAILABLE, STATUS_ADOPTED, STATUS_OWNED, STATUS_PENDING
@@ -73,6 +75,7 @@ public class PetDialogController {
@FXML @FXML
void initialize() { void initialize() {
cbPetStatus.setItems(statusList); cbPetStatus.setItems(statusList);
cbPetBreed.setDisable(true);
cbCustomer.setCellFactory(param -> new ListCell<>() { cbCustomer.setCellFactory(param -> new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) { @Override protected void updateItem(DropdownOption o, boolean empty) {
@@ -106,6 +109,18 @@ public class PetDialogController {
loadCustomers(); loadCustomers();
loadStores(); loadStores();
cbPetSpecies.valueProperty().addListener((obs, oldVal, newVal) -> {
if (newVal != null && !newVal.isBlank()) {
if (!cbPetSpecies.isDisabled()) cbPetBreed.setDisable(false);
loadBreeds(newVal);
} else {
cbPetBreed.setItems(FXCollections.observableArrayList());
cbPetBreed.setValue(null);
cbPetBreed.setPromptText("Select Species first");
if (!cbPetSpecies.isDisabled()) cbPetBreed.setDisable(true);
}
});
cbPetStatus.valueProperty().addListener((obs, oldVal, newVal) -> { cbPetStatus.valueProperty().addListener((obs, oldVal, newVal) -> {
if (newVal != null) applyStatusRules(newVal, true); if (newVal != null) applyStatusRules(newVal, true);
}); });
@@ -129,6 +144,29 @@ public class PetDialogController {
refreshImagePreview(); refreshImagePreview();
} }
private void loadBreeds(String species) {
cbPetBreed.setPromptText("Loading breeds...");
new Thread(() -> {
try {
List<DropdownOption> options = DropdownApi.getInstance().getPetBreeds(species);
List<String> breeds = options.stream().map(DropdownOption::getLabel).collect(Collectors.toList());
Platform.runLater(() -> {
cbPetBreed.setItems(FXCollections.observableArrayList(breeds));
cbPetBreed.setPromptText("Select Breed");
if (pendingBreedValue != null) {
cbPetBreed.setValue(pendingBreedValue);
pendingBreedValue = null;
}
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException("PetDialogController.loadBreeds", e, "Loading breeds for species: " + species);
cbPetBreed.setPromptText("Unable to load breeds");
});
}
}).start();
}
private void applyStatusRules(String status, boolean clearInvalidSelections) { private void applyStatusRules(String status, boolean clearInvalidSelections) {
if (STATUS_AVAILABLE.equalsIgnoreCase(status)) { if (STATUS_AVAILABLE.equalsIgnoreCase(status)) {
vbCustomerField.setDisable(true); vbCustomerField.setDisable(true);
@@ -151,9 +189,10 @@ public class PetDialogController {
errorMsg += Validator.isPresent(txtPetName.getText(), "Pet Name"); errorMsg += Validator.isPresent(txtPetName.getText(), "Pet Name");
errorMsg += Validator.isPresent(txtPetAge.getText(), "Age"); errorMsg += Validator.isPresent(txtPetAge.getText(), "Age");
errorMsg += Validator.isPresent(txtPetBreed.getText(), "Breed");
String speciesValue = cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : ""; String speciesValue = cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : "";
if (speciesValue.isEmpty()) errorMsg += "Species is required\n"; if (speciesValue.isEmpty()) errorMsg += "Species is required\n";
String breedValue = cbPetBreed.getValue() != null ? cbPetBreed.getValue().trim() : "";
if (breedValue.isEmpty()) errorMsg += "Breed is required\n";
if (cbPetStatus.getSelectionModel().getSelectedItem() == null) errorMsg += "Status is required\n"; if (cbPetStatus.getSelectionModel().getSelectedItem() == null) errorMsg += "Status is required\n";
errorMsg += Validator.isPresent(txtPetPrice.getText(), "Price"); errorMsg += Validator.isPresent(txtPetPrice.getText(), "Price");
@@ -171,7 +210,7 @@ public class PetDialogController {
errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50); errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50);
errorMsg += Validator.isLessThanVarChars(speciesValue, "Species", 50); errorMsg += Validator.isLessThanVarChars(speciesValue, "Species", 50);
errorMsg += Validator.isLessThanVarChars(txtPetBreed.getText(), "Breed", 50); errorMsg += Validator.isLessThanVarChars(breedValue, "Breed", 50);
errorMsg += Validator.isLessThanVarChars(txtPetPrice.getText(), "Price", 12); errorMsg += Validator.isLessThanVarChars(txtPetPrice.getText(), "Price", 12);
errorMsg += Validator.isLessThanVarChars(txtPetAge.getText(), "Age", 11); errorMsg += Validator.isLessThanVarChars(txtPetAge.getText(), "Age", 11);
errorMsg += Validator.isNonNegativeDouble(txtPetPrice.getText(), "Price"); errorMsg += Validator.isNonNegativeDouble(txtPetPrice.getText(), "Price");
@@ -224,7 +263,7 @@ public class PetDialogController {
PetRequest request = new PetRequest(); PetRequest request = new PetRequest();
request.setPetName(txtPetName.getText()); request.setPetName(txtPetName.getText());
request.setPetSpecies(cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : ""); request.setPetSpecies(cbPetSpecies.getValue() != null ? cbPetSpecies.getValue().trim() : "");
request.setPetBreed(txtPetBreed.getText()); request.setPetBreed(cbPetBreed.getValue() != null ? cbPetBreed.getValue().trim() : "");
request.setPetStatus(cbPetStatus.getValue()); request.setPetStatus(cbPetStatus.getValue());
if (txtPetPrice.getText() != null && !txtPetPrice.getText().isBlank()) { if (txtPetPrice.getText() != null && !txtPetPrice.getText().isBlank()) {
@@ -251,9 +290,7 @@ public class PetDialogController {
new Thread(() -> { new Thread(() -> {
try { try {
List<DropdownOption> options = DropdownApi.getInstance().getPetSpecies(); List<DropdownOption> options = DropdownApi.getInstance().getPetSpecies();
List<String> species = options.stream() List<String> species = options.stream().map(DropdownOption::getLabel).collect(Collectors.toList());
.map(DropdownOption::getLabel)
.collect(java.util.stream.Collectors.toList());
Platform.runLater(() -> { Platform.runLater(() -> {
String current = cbPetSpecies.getValue(); String current = cbPetSpecies.getValue();
cbPetSpecies.setItems(FXCollections.observableArrayList(species)); cbPetSpecies.setItems(FXCollections.observableArrayList(species));
@@ -339,8 +376,6 @@ public class PetDialogController {
lblPetId.setText("ID: " + pet.getPetId()); lblPetId.setText("ID: " + pet.getPetId());
txtPetName.setText(pet.getPetName()); txtPetName.setText(pet.getPetName());
cbPetSpecies.setValue(pet.getPetSpecies());
txtPetBreed.setText(pet.getPetBreed());
txtPetAge.setText(String.valueOf(pet.getPetAge())); txtPetAge.setText(String.valueOf(pet.getPetAge()));
txtPetPrice.setText(String.valueOf(pet.getPetPrice())); txtPetPrice.setText(String.valueOf(pet.getPetPrice()));
currentImageUrl = pet.getImageUrl(); currentImageUrl = pet.getImageUrl();
@@ -355,6 +390,9 @@ public class PetDialogController {
isOriginallyOwnedOrAdopted = STATUS_OWNED.equalsIgnoreCase(pet.getPetStatus()) isOriginallyOwnedOrAdopted = STATUS_OWNED.equalsIgnoreCase(pet.getPetStatus())
|| STATUS_ADOPTED.equalsIgnoreCase(pet.getPetStatus()); || STATUS_ADOPTED.equalsIgnoreCase(pet.getPetStatus());
pendingBreedValue = pet.getPetBreed();
cbPetSpecies.setValue(pet.getPetSpecies());
for (String status : cbPetStatus.getItems()) { for (String status : cbPetStatus.getItems()) {
if (status.equals(pet.getPetStatus())) { if (status.equals(pet.getPetStatus())) {
cbPetStatus.getSelectionModel().select(status); cbPetStatus.getSelectionModel().select(status);
@@ -368,7 +406,7 @@ public class PetDialogController {
private void applyEditModeLock() { private void applyEditModeLock() {
cbPetSpecies.setDisable(true); cbPetSpecies.setDisable(true);
txtPetBreed.setDisable(true); cbPetBreed.setDisable(true);
boolean isStaff = !UserSession.getInstance().isAdmin(); boolean isStaff = !UserSession.getInstance().isAdmin();
if (isStaff && isOriginallyOwnedOrAdopted) { if (isStaff && isOriginallyOwnedOrAdopted) {
@@ -388,7 +426,10 @@ public class PetDialogController {
selectedImageFile = null; selectedImageFile = null;
removeImageRequested = false; removeImageRequested = false;
cbPetSpecies.setDisable(false); cbPetSpecies.setDisable(false);
txtPetBreed.setDisable(false); cbPetBreed.setDisable(true);
cbPetBreed.setItems(FXCollections.observableArrayList());
cbPetBreed.setValue(null);
cbPetBreed.setPromptText("Select Species first");
cbPetStatus.setDisable(false); cbPetStatus.setDisable(false);
cbPetStatus.getSelectionModel().select(STATUS_AVAILABLE); cbPetStatus.getSelectionModel().select(STATUS_AVAILABLE);
applyStatusRules(STATUS_AVAILABLE, false); applyStatusRules(STATUS_AVAILABLE, false);

View File

@@ -82,6 +82,10 @@ public class StaffRegisterDialogController {
lblError.setText("Password is required."); lblError.setText("Password is required.");
return; return;
} }
if (password.length() < 6) {
lblError.setText("Password must be at least 6 characters.");
return;
}
if (!password.equals(confirm)) { if (!password.equals(confirm)) {
lblError.setText("Passwords do not match."); lblError.setText("Passwords do not match.");
return; return;

View File

@@ -91,7 +91,7 @@
<Font name="System Bold" size="16.0" /> <Font name="System Bold" size="16.0" />
</font> </font>
</Label> </Label>
<ComboBox fx:id="cbPetSpecies" editable="true" prefHeight="29.0" prefWidth="336.0" promptText="Select or enter species" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;"> <ComboBox fx:id="cbPetSpecies" prefHeight="29.0" prefWidth="336.0" promptText="Select Species" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding> <padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" /> <Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding> </padding>
@@ -105,11 +105,11 @@
<Font name="System Bold" size="16.0" /> <Font name="System Bold" size="16.0" />
</font> </font>
</Label> </Label>
<TextField fx:id="txtPetBreed" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10;"> <ComboBox fx:id="cbPetBreed" prefHeight="29.0" prefWidth="336.0" promptText="Select Species first" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding> <padding>
<Insets bottom="7.0" left="10.0" right="10.0" top="7.0" /> <Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding> </padding>
</TextField> </ComboBox>
</children> </children>
</VBox> </VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="1"> <VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="1">