added clendar to adoptions and appointments on desktop
This commit is contained in:
@@ -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(() -> {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
<?import org.example.petshopdesktop.ui.CalendarPane?>
|
||||
|
||||
<VBox minHeight="-Infinity" minWidth="-Infinity" spacing="20.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.AdoptionController">
|
||||
<padding>
|
||||
@@ -73,6 +74,7 @@
|
||||
</TextField>
|
||||
</children>
|
||||
</HBox>
|
||||
<CalendarPane fx:id="calendarPane" maxWidth="Infinity" />
|
||||
<Label fx:id="lblStatus" text="" textFill="#64748b" visible="false" managed="true">
|
||||
<font>
|
||||
<Font size="13.0" />
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
<?import org.example.petshopdesktop.ui.CalendarPane?>
|
||||
|
||||
<VBox minHeight="-Infinity" minWidth="-Infinity" spacing="20.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.AppointmentController">
|
||||
<padding>
|
||||
@@ -82,6 +83,7 @@
|
||||
</TextField>
|
||||
</children>
|
||||
</HBox>
|
||||
<CalendarPane fx:id="calendarPane" maxWidth="Infinity" />
|
||||
<Label fx:id="lblStatus" text="" textFill="#64748b" visible="false" managed="true">
|
||||
<font>
|
||||
<Font size="13.0" />
|
||||
|
||||
Reference in New Issue
Block a user