added clendar to adoptions and appointments on desktop

This commit is contained in:
Alex
2026-04-14 03:48:46 -06:00
parent a860a1c247
commit 4594139f8e
5 changed files with 276 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ import javafx.application.Platform;
import org.example.petshopdesktop.auth.UserSession;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
@@ -16,13 +17,16 @@ import org.example.petshopdesktop.api.dto.adoption.AdoptionResponse;
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.AdoptionDialogController;
import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.ui.CalendarPane;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.util.TableViewSupport;
import java.io.IOException;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
public class AdoptionController {
@@ -72,6 +76,11 @@ public class AdoptionController {
@FXML
private TextField txtSearch;
@FXML
private CalendarPane calendarPane;
private LocalDate selectedCalendarDate = null;
private FilteredList<Adoption> filteredAdoptions;
private ObservableList<Adoption> data = FXCollections.observableArrayList();
private String mode = null;
@@ -92,6 +101,9 @@ public class AdoptionController {
colStoreName.setCellValueFactory(new PropertyValueFactory<>("storeName"));
TableViewSupport.applyCurrencyColumn(colAdoptionFee);
filteredAdoptions = new FilteredList<>(data, a -> true);
TableViewSupport.bindSortedItems(tvAdoptions, filteredAdoptions);
displayAdoptions();
TableViewSupport.installDoubleClickAction(tvAdoptions, selected -> openDialog(selected, "Edit"));
@@ -106,6 +118,11 @@ public class AdoptionController {
displayFilteredAdoptions(newValue);
});
calendarPane.setOnDateSelected(date -> {
selectedCalendarDate = date;
filteredAdoptions.setPredicate(a -> date == null || a.getAdoptionDate().equals(date.toString()));
});
tvAdoptions.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
if (tvAdoptions.getSelectionModel().getSelectedItem() != null) {
@@ -118,6 +135,8 @@ public class AdoptionController {
@FXML
void btnRefresh(ActionEvent event) {
txtSearch.clear();
selectedCalendarDate = null;
if (filteredAdoptions != null) filteredAdoptions.setPredicate(a -> true);
tvAdoptions.getSortOrder().clear();
displayAdoptions();
TableViewSupport.flashStatus(lblStatus, "Refreshed");
@@ -198,7 +217,15 @@ public class AdoptionController {
Platform.runLater(() -> {
data.setAll(adoptionList);
tvAdoptions.setItems(data);
Set<LocalDate> dates = adoptionList.stream()
.filter(a -> a.getAdoptionDate() != null && !a.getAdoptionDate().isEmpty())
.map(a -> { try { return LocalDate.parse(a.getAdoptionDate()); } catch (Exception ex) { return null; } })
.filter(d -> d != null)
.collect(java.util.stream.Collectors.toSet());
calendarPane.setEventDates(dates);
if (selectedCalendarDate != null) {
filteredAdoptions.setPredicate(a -> a.getAdoptionDate().equals(selectedCalendarDate.toString()));
}
});
} catch (Exception e) {
Platform.runLater(() -> {
@@ -225,7 +252,15 @@ public class AdoptionController {
Platform.runLater(() -> {
data.setAll(adoptionList);
tvAdoptions.setItems(data);
Set<LocalDate> dates = adoptionList.stream()
.filter(a -> a.getAdoptionDate() != null && !a.getAdoptionDate().isEmpty())
.map(a -> { try { return LocalDate.parse(a.getAdoptionDate()); } catch (Exception ex) { return null; } })
.filter(d -> d != null)
.collect(java.util.stream.Collectors.toSet());
calendarPane.setEventDates(dates);
if (selectedCalendarDate != null) {
filteredAdoptions.setPredicate(a -> a.getAdoptionDate().equals(selectedCalendarDate.toString()));
}
});
} catch (Exception e) {
Platform.runLater(() -> {

View File

@@ -18,10 +18,13 @@ import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.api.dto.appointment.AppointmentResponse;
import org.example.petshopdesktop.api.endpoints.AppointmentApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController;
import org.example.petshopdesktop.ui.CalendarPane;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.util.TableViewSupport;
import java.time.LocalDate;
import java.util.List;
import java.util.Set;
import java.util.Comparator;
import java.util.stream.Collectors;
@@ -49,6 +52,9 @@ public class AppointmentController {
@FXML private TextField txtSearch;
@FXML private CalendarPane calendarPane;
private LocalDate selectedCalendarDate = null;
private final ObservableList<AppointmentDTO> appointments = FXCollections.observableArrayList();
private FilteredList<AppointmentDTO> filtered;
@@ -96,6 +102,11 @@ public class AppointmentController {
});
loadAppointments();
calendarPane.setOnDateSelected(date -> {
selectedCalendarDate = date;
filtered.setPredicate(apt -> date == null || apt.getAppointmentDate().equals(date.toString()));
});
}
@FXML
@@ -116,6 +127,15 @@ public class AppointmentController {
Platform.runLater(() -> {
appointments.setAll(appointmentDTOs);
Set<LocalDate> dates = appointmentDTOs.stream()
.filter(a -> a.getAppointmentDate() != null && !a.getAppointmentDate().isEmpty())
.map(a -> { try { return LocalDate.parse(a.getAppointmentDate()); } catch (Exception ex) { return null; } })
.filter(d -> d != null)
.collect(java.util.stream.Collectors.toSet());
calendarPane.setEventDates(dates);
if (selectedCalendarDate != null) {
filtered.setPredicate(apt -> apt.getAppointmentDate().equals(selectedCalendarDate.toString()));
}
});
}catch(Exception e){
Platform.runLater(() -> {
@@ -143,6 +163,15 @@ public class AppointmentController {
Platform.runLater(() -> {
appointments.setAll(appointmentDTOs);
Set<LocalDate> dates = appointmentDTOs.stream()
.filter(a -> a.getAppointmentDate() != null && !a.getAppointmentDate().isEmpty())
.map(a -> { try { return LocalDate.parse(a.getAppointmentDate()); } catch (Exception ex) { return null; } })
.filter(d -> d != null)
.collect(java.util.stream.Collectors.toSet());
calendarPane.setEventDates(dates);
if (selectedCalendarDate != null) {
filtered.setPredicate(apt -> apt.getAppointmentDate().equals(selectedCalendarDate.toString()));
}
});
} catch (Exception e) {
Platform.runLater(() -> {
@@ -159,6 +188,8 @@ public class AppointmentController {
@FXML
void btnRefresh(ActionEvent event) {
txtSearch.clear();
selectedCalendarDate = null;
filtered.setPredicate(a -> true);
tvAppointments.getSortOrder().clear();
loadAppointments();
TableViewSupport.flashStatus(lblStatus, "Refreshed");

View File

@@ -0,0 +1,204 @@
package org.example.petshopdesktop.ui;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
public class CalendarPane extends VBox {
private LocalDate currentDate = LocalDate.now();
private LocalDate selectedDate = null;
private Set<LocalDate> eventDates = new HashSet<>();
private boolean weekMode = true;
private Consumer<LocalDate> onDateSelected;
private Label lblHeader;
private GridPane dayGrid;
private Button btnToggle;
public CalendarPane() {
setSpacing(6);
setPadding(new Insets(10, 12, 10, 12));
setStyle("-fx-background-color: white; -fx-border-color: #e6e6e6; -fx-border-radius: 12; -fx-background-radius: 12;");
lblHeader = new Label();
lblHeader.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(lblHeader, Priority.ALWAYS);
lblHeader.setAlignment(Pos.CENTER);
lblHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 13;");
Button btnPrev = new Button("");
btnPrev.setStyle("-fx-background-color: transparent; -fx-font-size: 18; -fx-text-fill: #7f8c8d; -fx-cursor: hand;");
btnPrev.setOnAction(e -> navigate(-1));
Button btnNext = new Button("");
btnNext.setStyle("-fx-background-color: transparent; -fx-font-size: 18; -fx-text-fill: #7f8c8d; -fx-cursor: hand;");
btnNext.setOnAction(e -> navigate(1));
btnToggle = new Button("Month");
btnToggle.setStyle("-fx-background-color: #4ECDC4; -fx-text-fill: white; -fx-font-size: 11; -fx-font-weight: bold; -fx-background-radius: 6; -fx-cursor: hand;");
btnToggle.setPadding(new Insets(4, 10, 4, 10));
btnToggle.setOnAction(e -> {
weekMode = !weekMode;
btnToggle.setText(weekMode ? "Month" : "Week");
rebuild();
});
HBox navHeader = new HBox(4, btnPrev, lblHeader, btnNext, btnToggle);
navHeader.setAlignment(Pos.CENTER_LEFT);
GridPane dowHeader = new GridPane();
String[] days = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
for (int i = 0; i < 7; i++) {
ColumnConstraints cc = new ColumnConstraints();
cc.setHgrow(Priority.ALWAYS);
dowHeader.getColumnConstraints().add(cc);
Label lbl = new Label(days[i]);
lbl.setStyle("-fx-font-size: 11; -fx-text-fill: #95a5a6; -fx-font-weight: bold;");
lbl.setMaxWidth(Double.MAX_VALUE);
lbl.setAlignment(Pos.CENTER);
dowHeader.add(lbl, i, 0);
}
dayGrid = new GridPane();
dayGrid.setHgap(2);
dayGrid.setVgap(2);
getChildren().addAll(navHeader, dowHeader, dayGrid);
rebuild();
}
public void setEventDates(Set<LocalDate> dates) {
eventDates = new HashSet<>(dates);
rebuild();
}
public void setOnDateSelected(Consumer<LocalDate> handler) {
onDateSelected = handler;
}
private void navigate(int direction) {
if (weekMode) {
currentDate = currentDate.plusWeeks(direction);
} else {
currentDate = currentDate.plusMonths(direction);
}
rebuild();
}
private void rebuild() {
dayGrid.getChildren().clear();
dayGrid.getColumnConstraints().clear();
for (int i = 0; i < 7; i++) {
ColumnConstraints cc = new ColumnConstraints();
cc.setHgrow(Priority.ALWAYS);
cc.setFillWidth(true);
dayGrid.getColumnConstraints().add(cc);
}
if (weekMode) {
int dow = currentDate.getDayOfWeek().getValue() % 7;
LocalDate startOfWeek = currentDate.minusDays(dow);
lblHeader.setText("Week of " + startOfWeek.format(DateTimeFormatter.ofPattern("MMM d, yyyy")));
for (int col = 0; col < 7; col++) {
dayGrid.add(buildDayCell(startOfWeek.plusDays(col)), col, 0);
}
} else {
YearMonth ym = YearMonth.of(currentDate.getYear(), currentDate.getMonthValue());
lblHeader.setText(currentDate.format(DateTimeFormatter.ofPattern("MMMM yyyy")));
int firstDow = ym.atDay(1).getDayOfWeek().getValue() % 7;
int daysInMonth = ym.lengthOfMonth();
int rows = (int) Math.ceil((firstDow + daysInMonth) / 7.0);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < 7; col++) {
int dayNum = row * 7 + col - firstDow + 1;
if (dayNum >= 1 && dayNum <= daysInMonth) {
dayGrid.add(buildDayCell(ym.atDay(dayNum)), col, row);
} else {
Region spacer = new Region();
spacer.setPrefSize(36, 36);
dayGrid.add(spacer, col, row);
}
}
}
}
}
private StackPane buildDayCell(LocalDate date) {
boolean isSelected = date.equals(selectedDate);
boolean isToday = date.equals(LocalDate.now());
boolean hasEvent = eventDates.contains(date);
StackPane cell = new StackPane();
cell.setPrefSize(36, 36);
cell.setMaxWidth(Double.MAX_VALUE);
String baseStyle;
String labelColor;
String labelWeight = "-fx-font-weight: bold;";
if (isSelected) {
baseStyle = "-fx-background-color: #4ECDC4; -fx-background-radius: 8; -fx-cursor: hand;";
labelColor = "-fx-text-fill: white;";
} else if (isToday) {
baseStyle = "-fx-background-color: #e8faf8; -fx-background-radius: 8; -fx-border-color: #4ECDC4; -fx-border-radius: 8; -fx-border-width: 1.5; -fx-cursor: hand;";
labelColor = "-fx-text-fill: #2abb9b;";
} else {
baseStyle = "-fx-background-radius: 8; -fx-cursor: hand;";
labelColor = "-fx-text-fill: #2c3e50;";
labelWeight = "";
}
cell.setStyle(baseStyle);
Label dayLabel = new Label(String.valueOf(date.getDayOfMonth()));
dayLabel.setStyle("-fx-font-size: 12; " + labelColor + labelWeight);
VBox inner = new VBox(1);
inner.setAlignment(Pos.CENTER);
inner.getChildren().add(dayLabel);
if (hasEvent) {
Circle dot = new Circle(3, Color.web("#FF6b6b"));
inner.getChildren().add(dot);
} else {
Region spacer = new Region();
spacer.setPrefHeight(6);
inner.getChildren().add(spacer);
}
cell.getChildren().add(inner);
if (!isSelected && !isToday) {
cell.setOnMouseEntered(e -> cell.setStyle("-fx-background-color: #f0f9f8; -fx-background-radius: 8; -fx-cursor: hand;"));
cell.setOnMouseExited(e -> cell.setStyle(baseStyle));
}
cell.setOnMouseClicked(e -> {
if (date.equals(selectedDate)) {
selectedDate = null;
} else {
selectedDate = date;
currentDate = date;
}
rebuild();
if (onDateSelected != null) {
onDateSelected.accept(selectedDate);
}
});
return cell;
}
}