merge origin/main into morefiles, resolve all conflicts

This commit is contained in:
2026-04-07 20:29:54 -06:00
376 changed files with 30110 additions and 9105 deletions

View File

@@ -15,16 +15,19 @@ public class AppointmentDTO {
private SimpleIntegerProperty serviceId;
private SimpleStringProperty serviceName;
private SimpleIntegerProperty employeeId;
private SimpleStringProperty employeeName;
private SimpleStringProperty appointmentDate;
private SimpleStringProperty appointmentTime;
private SimpleStringProperty appointmentStatus;
// Constructor
public AppointmentDTO(int appointmentId,
int customerId, String customerName,
int petId, String petName,
int serviceId, String serviceName,
int employeeId,
String employeeName,
String appointmentDate,
String appointmentTime,
String appointmentStatus) {
@@ -36,12 +39,13 @@ public class AppointmentDTO {
this.petName = new SimpleStringProperty(petName);
this.serviceId = new SimpleIntegerProperty(serviceId);
this.serviceName = new SimpleStringProperty(serviceName);
this.employeeId = new SimpleIntegerProperty(employeeId);
this.employeeName = new SimpleStringProperty(employeeName);
this.appointmentDate = new SimpleStringProperty(appointmentDate);
this.appointmentTime = new SimpleStringProperty(appointmentTime);
this.appointmentStatus = new SimpleStringProperty(appointmentStatus);
}
// Getters
public int getAppointmentId() { return appointmentId.get(); }
public int getCustomerId() { return customerId.get(); }
@@ -52,8 +56,10 @@ public class AppointmentDTO {
public int getServiceId() { return serviceId.get(); }
public String getServiceName() { return serviceName.get(); }
public int getEmployeeId() { return employeeId.get(); }
public String getEmployeeName() { return employeeName.get(); }
public String getAppointmentDate() { return appointmentDate.get(); }
public String getAppointmentTime() { return appointmentTime.get(); }
public String getAppointmentStatus() { return appointmentStatus.get(); }
}
}

View File

@@ -5,6 +5,8 @@ import java.time.LocalDate;
public class AdoptionRequest {
private Long petId;
private Long customerId;
private Long employeeId;
private Long sourceStoreId;
private LocalDate adoptionDate;
private String adoptionStatus;
@@ -27,6 +29,22 @@ public class AdoptionRequest {
this.customerId = customerId;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public Long getSourceStoreId() {
return sourceStoreId;
}
public void setSourceStoreId(Long sourceStoreId) {
this.sourceStoreId = sourceStoreId;
}
public LocalDate getAdoptionDate() {
return adoptionDate;
}

View File

@@ -6,8 +6,10 @@ public class AdoptionResponse {
private Long adoptionId;
private Long petId;
private Long customerId;
private Long employeeId;
private String petName;
private String customerName;
private String employeeName;
private LocalDate adoptionDate;
private java.math.BigDecimal adoptionFee;
private String adoptionStatus;
@@ -39,6 +41,14 @@ public class AdoptionResponse {
this.customerId = customerId;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public String getPetName() {
return petName;
}
@@ -55,6 +65,14 @@ public class AdoptionResponse {
this.customerName = customerName;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public LocalDate getAdoptionDate() {
return adoptionDate;
}

View File

@@ -8,6 +8,8 @@ public class DashboardResponse {
private InventorySummary inventorySummary;
private List<TopProduct> topProducts;
private List<DailySales> dailySales;
private List<PaymentMethodData> paymentMethods;
private List<EmployeePerformanceData> employeePerformance;
public DashboardResponse() {
}
@@ -44,11 +46,28 @@ public class DashboardResponse {
this.dailySales = dailySales;
}
public List<PaymentMethodData> getPaymentMethods() {
return paymentMethods;
}
public void setPaymentMethods(List<PaymentMethodData> paymentMethods) {
this.paymentMethods = paymentMethods;
}
public List<EmployeePerformanceData> getEmployeePerformance() {
return employeePerformance;
}
public void setEmployeePerformance(List<EmployeePerformanceData> employeePerformance) {
this.employeePerformance = employeePerformance;
}
public static class SalesSummary {
private BigDecimal totalRevenue;
private Long totalSales;
private BigDecimal totalRefunds;
private Long totalRefundCount;
private Long totalItemsSold;
public SalesSummary() {
}
@@ -84,6 +103,14 @@ public class DashboardResponse {
public void setTotalRefundCount(Long totalRefundCount) {
this.totalRefundCount = totalRefundCount;
}
public Long getTotalItemsSold() {
return totalItemsSold;
}
public void setTotalItemsSold(Long totalItemsSold) {
this.totalItemsSold = totalItemsSold;
}
}
public static class InventorySummary {
@@ -118,4 +145,46 @@ public class DashboardResponse {
this.outOfStockProducts = outOfStockProducts;
}
}
public static class PaymentMethodData {
private String paymentMethod;
private Long count;
public String getPaymentMethod() {
return paymentMethod;
}
public void setPaymentMethod(String paymentMethod) {
this.paymentMethod = paymentMethod;
}
public Long getCount() {
return count;
}
public void setCount(Long count) {
this.count = count;
}
}
public static class EmployeePerformanceData {
private String employeeName;
private BigDecimal revenue;
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public BigDecimal getRevenue() {
return revenue;
}
public void setRevenue(BigDecimal revenue) {
this.revenue = revenue;
}
}
}

View File

@@ -2,28 +2,20 @@ package org.example.petshopdesktop.api.dto.appointment;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
public class AppointmentRequest {
private List<Long> petIds;
private Long customerId;
private Long storeId;
private Long serviceId;
private Long employeeId;
private LocalDate appointmentDate;
private LocalTime appointmentTime;
private String appointmentStatus;
private Long petId;
public AppointmentRequest() {
}
public List<Long> getPetIds() {
return petIds;
}
public void setPetIds(List<Long> petIds) {
this.petIds = petIds;
}
public Long getCustomerId() {
return customerId;
}
@@ -48,6 +40,14 @@ public class AppointmentRequest {
this.serviceId = serviceId;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}
@@ -71,4 +71,12 @@ public class AppointmentRequest {
public void setAppointmentStatus(String appointmentStatus) {
this.appointmentStatus = appointmentStatus;
}
public Long getPetId() {
return petId;
}
public void setPetId(Long petId) {
this.petId = petId;
}
}

View File

@@ -10,12 +10,14 @@ public class AppointmentResponse {
private Long storeId;
private String storeName;
private Long serviceId;
private java.util.List<String> petNames;
private java.util.List<Long> petIds;
private String serviceName;
private Long employeeId;
private String employeeName;
private LocalDate appointmentDate;
private LocalTime appointmentTime;
private String appointmentStatus;
private String petName;
private Long petId;
public AppointmentResponse() {
}
@@ -68,20 +70,20 @@ public class AppointmentResponse {
this.serviceId = serviceId;
}
public java.util.List<String> getPetNames() {
return petNames;
public String getPetName() {
return petName;
}
public void setPetNames(java.util.List<String> petNames) {
this.petNames = petNames;
public void setPetName(String petName) {
this.petName = petName;
}
public java.util.List<Long> getPetIds() {
return petIds;
public Long getPetId() {
return petId;
}
public void setPetIds(java.util.List<Long> petIds) {
this.petIds = petIds;
public void setPetId(Long petId) {
this.petId = petId;
}
public String getServiceName() {
@@ -92,6 +94,22 @@ public class AppointmentResponse {
this.serviceName = serviceName;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public LocalDate getAppointmentDate() {
return appointmentDate;
}

View File

@@ -2,6 +2,10 @@ package org.example.petshopdesktop.api.dto.chat;
public class MessageRequest {
private String content;
private String attachmentUrl;
private String attachmentName;
private String attachmentMimeType;
private Long attachmentSizeBytes;
public MessageRequest() {
}
@@ -17,4 +21,36 @@ public class MessageRequest {
public void setContent(String content) {
this.content = content;
}
public String getAttachmentUrl() {
return attachmentUrl;
}
public void setAttachmentUrl(String attachmentUrl) {
this.attachmentUrl = attachmentUrl;
}
public String getAttachmentName() {
return attachmentName;
}
public void setAttachmentName(String attachmentName) {
this.attachmentName = attachmentName;
}
public String getAttachmentMimeType() {
return attachmentMimeType;
}
public void setAttachmentMimeType(String attachmentMimeType) {
this.attachmentMimeType = attachmentMimeType;
}
public Long getAttachmentSizeBytes() {
return attachmentSizeBytes;
}
public void setAttachmentSizeBytes(Long attachmentSizeBytes) {
this.attachmentSizeBytes = attachmentSizeBytes;
}
}

View File

@@ -9,6 +9,10 @@ public class MessageResponse {
private String content;
private LocalDateTime timestamp;
private Boolean isRead;
private String attachmentUrl;
private String attachmentName;
private String attachmentMimeType;
private Long attachmentSizeBytes;
public MessageResponse() {
}
@@ -60,4 +64,36 @@ public class MessageResponse {
public void setIsRead(Boolean isRead) {
this.isRead = isRead;
}
public String getAttachmentUrl() {
return attachmentUrl;
}
public void setAttachmentUrl(String attachmentUrl) {
this.attachmentUrl = attachmentUrl;
}
public String getAttachmentName() {
return attachmentName;
}
public void setAttachmentName(String attachmentName) {
this.attachmentName = attachmentName;
}
public String getAttachmentMimeType() {
return attachmentMimeType;
}
public void setAttachmentMimeType(String attachmentMimeType) {
this.attachmentMimeType = attachmentMimeType;
}
public Long getAttachmentSizeBytes() {
return attachmentSizeBytes;
}
public void setAttachmentSizeBytes(Long attachmentSizeBytes) {
this.attachmentSizeBytes = attachmentSizeBytes;
}
}

View File

@@ -9,15 +9,14 @@ import java.util.List;
public class PageResponse<T> {
private List<T> content;
@JsonProperty("number")
private int pageNumber;
@JsonProperty("size")
private int pageSize;
private long totalElements;
private int totalPages;
private boolean last;
private PageMetadata page;
public PageResponse() {
}
@@ -63,10 +62,71 @@ public class PageResponse<T> {
}
public boolean isLast() {
return last;
if (last) {
return true;
}
if (page != null) {
return page.number >= Math.max(0, page.totalPages - 1);
}
return content == null || content.isEmpty();
}
public void setLast(boolean last) {
this.last = last;
}
public PageMetadata getPage() {
return page;
}
public void setPage(PageMetadata page) {
this.page = page;
if (page != null) {
this.pageNumber = page.number;
this.pageSize = page.size;
this.totalElements = page.totalElements;
this.totalPages = page.totalPages;
this.last = page.number >= Math.max(0, page.totalPages - 1);
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class PageMetadata {
private int size;
private int number;
private long totalElements;
private int totalPages;
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public long getTotalElements() {
return totalElements;
}
public void setTotalElements(long totalElements) {
this.totalElements = totalElements;
}
public int getTotalPages() {
return totalPages;
}
public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}
}
}

View File

@@ -5,9 +5,12 @@ public class EmployeeRequest {
private String password;
private String firstName;
private String lastName;
private String fullName;
private String email;
private String phone;
private String role;
private String staffRole;
private Long primaryStoreId;
private Boolean active;
public String getUsername() { return username; }
@@ -18,12 +21,18 @@ public class EmployeeRequest {
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public String getStaffRole() { return staffRole; }
public void setStaffRole(String staffRole) { this.staffRole = staffRole; }
public Long getPrimaryStoreId() { return primaryStoreId; }
public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; }
public Boolean getActive() { return active; }
public void setActive(Boolean active) { this.active = active; }
}

View File

@@ -3,6 +3,7 @@ package org.example.petshopdesktop.api.dto.employee;
import java.time.LocalDateTime;
public class EmployeeResponse {
private Long id;
private Long employeeId;
private Long userId;
private String username;
@@ -12,13 +13,17 @@ public class EmployeeResponse {
private String email;
private String phone;
private String role;
private String staffRole;
private Long primaryStoreId;
private Boolean active;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Long getEmployeeId() { return employeeId; }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getEmployeeId() { return employeeId != null ? employeeId : id; }
public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; }
public Long getUserId() { return userId; }
public Long getUserId() { return userId != null ? userId : id; }
public void setUserId(Long userId) { this.userId = userId; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
@@ -34,6 +39,10 @@ public class EmployeeResponse {
public void setPhone(String phone) { this.phone = phone; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public String getStaffRole() { return staffRole; }
public void setStaffRole(String staffRole) { this.staffRole = staffRole; }
public Long getPrimaryStoreId() { return primaryStoreId; }
public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; }
public Boolean getActive() { return active; }
public void setActive(Boolean active) { this.active = active; }
public LocalDateTime getCreatedAt() { return createdAt; }

View File

@@ -3,6 +3,7 @@ package org.example.petshopdesktop.api.dto.inventory;
public class InventoryRequest {
private Long prodId;
private Integer quantity;
private Long storeId;
public InventoryRequest() {
}
@@ -22,4 +23,12 @@ public class InventoryRequest {
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
}

View File

@@ -7,6 +7,8 @@ public class InventoryResponse {
private Long prodId;
private String productName;
private String categoryName;
private Long storeId;
private String storeName;
private Integer quantity;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@@ -46,6 +48,22 @@ public class InventoryResponse {
this.categoryName = categoryName;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public Integer getQuantity() {
return quantity;
}

View File

@@ -9,6 +9,8 @@ public class PetRequest {
private Integer petAge;
private String petStatus;
private BigDecimal petPrice;
private Long customerId;
private Long storeId;
public PetRequest() {
}
@@ -60,4 +62,20 @@ public class PetRequest {
public void setPetPrice(BigDecimal petPrice) {
this.petPrice = petPrice;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
}

View File

@@ -14,6 +14,10 @@ public class PetResponse {
private String imageUrl;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private Long customerId;
private String customerName;
private Long storeId;
private String storeName;
public PetResponse() {
}
@@ -97,4 +101,36 @@ public class PetResponse {
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
}

View File

@@ -26,6 +26,22 @@ public class DropdownApi {
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getProductCategories() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/product-categories");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from product categories endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getPetSpecies() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/pet-species");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from pet species endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getProducts() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/products");
if (response == null || response.isEmpty()) {
@@ -58,6 +74,14 @@ public class DropdownApi {
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getAppointmentCustomers() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/appointment-customers");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from appointment customers endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getPets() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/pets");
if (response == null || response.isEmpty()) {
@@ -66,6 +90,22 @@ public class DropdownApi {
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getAdoptionPets() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/adoption-pets");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from adoption pets endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getCustomerPets(Long customerId) throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/customers/" + customerId + "/pets");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from customer pets endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getStores() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/stores");
if (response == null || response.isEmpty()) {
@@ -73,4 +113,20 @@ public class DropdownApi {
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getStoreEmployees(Long storeId) throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/stores/" + storeId + "/employees");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from store employees endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
public List<DropdownOption> getEmployees() throws Exception {
String response = apiClient.getRawResponse("/api/v1/dropdowns/employees");
if (response == null || response.isEmpty()) {
throw new IllegalStateException("Empty response from all employees endpoint");
}
return apiClient.getObjectMapper().readValue(response, new TypeReference<List<DropdownOption>>() {});
}
}

View File

@@ -24,11 +24,20 @@ public class PetApi {
return INSTANCE;
}
public List<PetResponse> listPets(String query) throws Exception {
public List<PetResponse> listPets(String query, String species, String status, Long storeId) throws Exception {
String path = "/api/v1/pets?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
if (species != null && !species.isEmpty()) {
path += "&species=" + URLEncoder.encode(species, StandardCharsets.UTF_8);
}
if (status != null && !status.isEmpty()) {
path += "&status=" + URLEncoder.encode(status, StandardCharsets.UTF_8);
}
if (storeId != null) {
path += "&storeId=" + storeId;
}
String response = apiClient.getRawResponse(path);
PageResponse<PetResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
@@ -40,6 +49,14 @@ public class PetApi {
return pageResponse.getContent();
}
public List<PetResponse> listPets(String query, String species, String status) throws Exception {
return listPets(query, species, status, null);
}
public List<PetResponse> listPets(String query) throws Exception {
return listPets(query, null, null, null);
}
public PetResponse createPet(PetRequest request) throws Exception {
return apiClient.post("/api/v1/pets", request, PetResponse.class);
}

View File

@@ -24,11 +24,14 @@ public class ProductApi {
return INSTANCE;
}
public List<ProductResponse> listProducts(String query) throws Exception {
public List<ProductResponse> listProducts(String query, Long categoryId) throws Exception {
String path = "/api/v1/products?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
if (categoryId != null) {
path += "&categoryId=" + categoryId;
}
String response = apiClient.getRawResponse(path);
PageResponse<ProductResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
@@ -40,6 +43,10 @@ public class ProductApi {
return pageResponse.getContent();
}
public List<ProductResponse> listProducts(String query) throws Exception {
return listProducts(query, null);
}
public ProductResponse createProduct(ProductRequest request) throws Exception {
return apiClient.post("/api/v1/products", request, ProductResponse.class);
}

View File

@@ -38,6 +38,39 @@ public class SaleApi {
return pageResponse.getContent();
}
public List<SaleResponse> listAllSales(String query) throws Exception {
int page = 0;
int size = 250;
List<SaleResponse> allSales = new java.util.ArrayList<>();
while (true) {
String path = "/api/v1/sales?page=" + page + "&size=" + size;
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
}
String response = apiClient.getRawResponse(path);
PageResponse<SaleResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,
new TypeReference<PageResponse<SaleResponse>>() {}
);
if (pageResponse == null) {
throw new IllegalStateException("Null response from sales endpoint");
}
if (pageResponse.getContent() != null) {
allSales.addAll(pageResponse.getContent());
}
if (pageResponse.isLast()) {
break;
}
page++;
}
return allSales;
}
public SaleResponse getSale(Long id) throws Exception {
return apiClient.get("/api/v1/sales/" + id, SaleResponse.class);
}

View File

@@ -92,4 +92,8 @@ public class UserSession {
public boolean isAdmin() {
return Role.ADMIN.equals(role);
}
public boolean isStaff() {
return Role.STAFF.equals(role);
}
}

View File

@@ -18,6 +18,7 @@ import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -42,6 +43,9 @@ public class AdoptionController {
@FXML
private TableColumn<Adoption, String> colCustomerName;
@FXML
private TableColumn<Adoption, String> colEmployeeName;
@FXML
private TableColumn<Adoption, String> colAdoptionDate;
@@ -64,12 +68,13 @@ public class AdoptionController {
void initialize() {
btnEdit.setDisable(true);
btnDelete.setDisable(true);
//Enable multiple selection
tvAdoptions.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
colAdoptionId.setCellValueFactory(new PropertyValueFactory<>("adoptionId"));
colPetId.setCellValueFactory(new PropertyValueFactory<>("petName"));
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
colAdoptionDate.setCellValueFactory(new PropertyValueFactory<>("adoptionDate"));
colAdoptionFee.setCellValueFactory(new PropertyValueFactory<>("adoptionFee"));
colAdoptionStatus.setCellValueFactory(new PropertyValueFactory<>("adoptionStatus"));
@@ -86,7 +91,6 @@ public class AdoptionController {
displayFilteredAdoptions(newValue);
});
//EventListener for DELETE key
tvAdoptions.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
if (tvAdoptions.getSelectionModel().getSelectedItem() != null) {
@@ -104,11 +108,10 @@ public class AdoptionController {
@FXML
void btnDeleteClicked(ActionEvent event) {
//get selected adoptions
var selectedAdoptions = tvAdoptions.getSelectionModel().getSelectedItems();
if (selectedAdoptions.isEmpty()) return;
//ask user to confirm
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete");
String message = selectedAdoptions.size() == 1
@@ -118,7 +121,6 @@ public class AdoptionController {
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
Optional<ButtonType> result = question.showAndWait();
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
List<Long> ids = selectedAdoptions.stream()
.map(a -> (long) a.getAdoptionId())
@@ -141,7 +143,6 @@ public class AdoptionController {
alert.showAndWait();
}
//refresh display and reset inputs
displayAdoptions();
btnDelete.setDisable(true);
btnEdit.setDisable(true);
@@ -168,6 +169,7 @@ public class AdoptionController {
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(filter);
List<Adoption> adoptionList = adoptions.stream()
.map(this::mapToAdoption)
.sorted(Comparator.comparing(Adoption::getAdoptionDate).reversed())
.collect(Collectors.toList());
Platform.runLater(() -> {
@@ -193,6 +195,7 @@ public class AdoptionController {
List<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(null);
List<Adoption> adoptionList = adoptions.stream()
.map(this::mapToAdoption)
.sorted(Comparator.comparing(Adoption::getAdoptionDate).reversed())
.collect(Collectors.toList());
Platform.runLater(() -> {
@@ -249,8 +252,10 @@ public class AdoptionController {
response.getAdoptionId().intValue(),
response.getPetId() != null ? response.getPetId().intValue() : 0,
response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
response.getEmployeeId() != null ? response.getEmployeeId().intValue() : 0,
response.getPetName(),
response.getCustomerName(),
response.getEmployeeName(),
response.getAdoptionDate() != null ? response.getAdoptionDate().toString() : "",
response.getAdoptionFee() != null ? response.getAdoptionFee().doubleValue() : 0.0,
response.getAdoptionStatus()

View File

@@ -9,9 +9,11 @@ import javafx.scene.control.Label;
import org.example.petshopdesktop.api.dto.analytics.DailySales;
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
import org.example.petshopdesktop.api.dto.analytics.TopProduct;
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.api.endpoints.AnalyticsApi;
import org.example.petshopdesktop.api.endpoints.SaleApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.util.ActivityLogger;
import java.math.BigDecimal;
@@ -127,16 +129,17 @@ public class AnalyticsController {
new Thread(() -> {
try {
DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
Platform.runLater(() -> {
try {
boolean isAdmin = UserSession.getInstance().isAdmin();
loadSummaryData(dashboard);
loadSalesOverTime(dashboard);
loadTopProductsByRevenue(dashboard);
loadTopProductsByQuantity(dashboard);
loadPaymentMethodDistribution(sales);
loadEmployeePerformance(sales);
loadPaymentMethodDistribution(dashboard);
loadEmployeePerformance(dashboard, isAdmin);
applyRoleVisibility(isAdmin);
} catch (Exception e) {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
lblError.setText("Error loading analytics data. Please try again.");
@@ -144,6 +147,31 @@ public class AnalyticsController {
}
});
} catch (Exception e) {
if (UserSession.getInstance().isStaff()) {
try {
DashboardResponse fallback = buildStaffFallbackDashboard();
Platform.runLater(() -> {
try {
loadSummaryData(fallback);
loadSalesOverTime(fallback);
loadTopProductsByRevenue(fallback);
loadTopProductsByQuantity(fallback);
loadPaymentMethodDistribution(fallback);
loadEmployeePerformance(fallback, false);
applyRoleVisibility(false);
lblError.setVisible(false);
} catch (Exception inner) {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", inner, "Rendering fallback analytics data");
lblError.setText("Error loading analytics data. Please try again.");
lblError.setVisible(true);
}
});
return;
} catch (Exception fallbackError) {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", fallbackError, "Loading fallback analytics data");
}
}
Platform.runLater(() -> {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
lblError.setText("Error loading analytics data. Please try again.");
@@ -157,15 +185,12 @@ public class AnalyticsController {
if (dashboard != null) {
BigDecimal totalRevenue = BigDecimal.ZERO;
Long totalSales = 0L;
Long totalProducts = 0L;
if (dashboard.getSalesSummary() != null) {
totalRevenue = dashboard.getSalesSummary().getTotalRevenue() != null ? dashboard.getSalesSummary().getTotalRevenue() : BigDecimal.ZERO;
totalSales = dashboard.getSalesSummary().getTotalSales() != null ? dashboard.getSalesSummary().getTotalSales() : 0L;
}
if (dashboard.getInventorySummary() != null) {
totalProducts = dashboard.getInventorySummary().getTotalProducts() != null ? dashboard.getInventorySummary().getTotalProducts() : 0L;
lblTotalItems.setText(wholeNumber.format(dashboard.getSalesSummary().getTotalItemsSold() != null ? dashboard.getSalesSummary().getTotalItemsSold() : 0L));
} else {
lblTotalItems.setText(wholeNumber.format(0));
}
lblTotalRevenue.setText(currency.format(totalRevenue));
@@ -176,7 +201,6 @@ public class AnalyticsController {
avgTransaction = totalRevenue.divide(BigDecimal.valueOf(totalSales), 2, RoundingMode.HALF_UP);
}
lblAvgTransaction.setText(currency.format(avgTransaction));
lblTotalItems.setText(wholeNumber.format(totalProducts));
}
}
@@ -243,24 +267,14 @@ public class AnalyticsController {
applyBarChartColor(chartTopQuantity, QUANTITY_COLOR);
}
private void loadPaymentMethodDistribution(List<SaleResponse> sales) throws Exception {
Map<String, Long> paymentMethodCount = sales.stream()
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
.collect(Collectors.groupingBy(
sale -> sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "Unknown",
Collectors.counting()
));
private void loadPaymentMethodDistribution(DashboardResponse dashboard) throws Exception {
chartPaymentMethods.getData().clear();
List<Map.Entry<String, Long>> paymentEntries = paymentMethodCount.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.toList();
for (Map.Entry<String, Long> entry : paymentEntries) {
List<DashboardResponse.PaymentMethodData> paymentEntries = dashboard.getPaymentMethods() != null ? dashboard.getPaymentMethods() : List.of();
for (DashboardResponse.PaymentMethodData entry : paymentEntries) {
PieChart.Data slice = new PieChart.Data(
entry.getKey() + " (" + entry.getValue() + ")",
entry.getValue()
entry.getPaymentMethod() + " (" + entry.getCount() + ")",
entry.getCount()
);
chartPaymentMethods.getData().add(slice);
}
@@ -269,24 +283,14 @@ public class AnalyticsController {
applyPieChartColors();
}
private void loadEmployeePerformance(List<SaleResponse> sales) throws Exception {
Map<String, Double> employeeRevenue = sales.stream()
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
.filter(sale -> sale.getEmployeeName() != null)
.collect(Collectors.groupingBy(
SaleResponse::getEmployeeName,
Collectors.summingDouble(sale -> sale.getTotalAmount() != null ? sale.getTotalAmount().doubleValue() : 0.0)
));
private void loadEmployeePerformance(DashboardResponse dashboard, boolean isAdmin) throws Exception {
XYChart.Series<String, Number> series = new XYChart.Series<>();
series.setName("Revenue");
series.setName(isAdmin ? "Revenue" : "My Revenue");
List<Map.Entry<String, Double>> employeeEntries = employeeRevenue.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.toList();
for (Map.Entry<String, Double> entry : employeeEntries) {
series.getData().add(new XYChart.Data<>(entry.getKey(), entry.getValue()));
List<DashboardResponse.EmployeePerformanceData> employeeEntries = dashboard.getEmployeePerformance() != null ? dashboard.getEmployeePerformance() : List.of();
for (DashboardResponse.EmployeePerformanceData entry : employeeEntries) {
BigDecimal revenue = entry.getRevenue() != null ? entry.getRevenue() : BigDecimal.ZERO;
series.getData().add(new XYChart.Data<>(entry.getEmployeeName(), revenue));
}
chartEmployeePerformance.getData().clear();
@@ -294,6 +298,139 @@ public class AnalyticsController {
applyBarChartColor(chartEmployeePerformance, EMPLOYEE_COLOR);
}
private void applyRoleVisibility(boolean isAdmin) {
chartEmployeePerformance.setVisible(isAdmin);
chartEmployeePerformance.setManaged(isAdmin);
if (chartEmployeePerformance.getParent() != null) {
chartEmployeePerformance.getParent().setVisible(isAdmin);
chartEmployeePerformance.getParent().setManaged(isAdmin);
}
}
private DashboardResponse buildStaffFallbackDashboard() throws Exception {
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
String employeeName = UserSession.getInstance().getEmployeeName();
if (employeeName == null || employeeName.isBlank()) {
employeeName = UserSession.getInstance().getUsername();
}
final String employeeNameFilter = employeeName;
List<SaleResponse> personalSales = sales.stream()
.filter(sale -> sale.getEmployeeName() != null && sale.getEmployeeName().equalsIgnoreCase(employeeNameFilter))
.toList();
DashboardResponse dashboard = new DashboardResponse();
dashboard.setSalesSummary(buildSalesSummary(personalSales));
dashboard.setDailySales(buildDailySales(personalSales, 30));
dashboard.setTopProducts(buildTopProducts(personalSales, 10));
dashboard.setPaymentMethods(buildPaymentMethods(personalSales));
dashboard.setEmployeePerformance(List.of(new DashboardResponse.EmployeePerformanceData()));
return dashboard;
}
private DashboardResponse.SalesSummary buildSalesSummary(List<SaleResponse> sales) {
DashboardResponse.SalesSummary summary = new DashboardResponse.SalesSummary();
BigDecimal totalRevenue = BigDecimal.ZERO;
long totalSales = 0L;
BigDecimal totalRefunds = BigDecimal.ZERO;
long totalRefundCount = 0L;
long totalItemsSold = 0L;
for (SaleResponse sale : sales) {
boolean refund = Boolean.TRUE.equals(sale.getIsRefund());
BigDecimal amount = sale.getTotalAmount() != null ? sale.getTotalAmount() : BigDecimal.ZERO;
if (refund) {
totalRefunds = totalRefunds.add(amount);
totalRefundCount++;
continue;
}
totalRevenue = totalRevenue.add(amount);
totalSales++;
if (sale.getItems() != null) {
totalItemsSold += sale.getItems().stream().mapToLong(item -> item.getQuantity() == null ? 0 : item.getQuantity()).sum();
}
}
summary.setTotalRevenue(totalRevenue);
summary.setTotalSales(totalSales);
summary.setTotalRefunds(totalRefunds);
summary.setTotalRefundCount(totalRefundCount);
summary.setTotalItemsSold(totalItemsSold);
return summary;
}
private List<DailySales> buildDailySales(List<SaleResponse> sales, int days) {
Map<LocalDate, DailySales> daily = new LinkedHashMap<>();
LocalDate start = LocalDate.now().minusDays(days - 1L);
for (int i = 0; i < days; i++) {
LocalDate date = start.plusDays(i);
DailySales row = new DailySales();
row.setDate(date.toString());
row.setRevenue(BigDecimal.ZERO);
row.setSalesCount(0L);
daily.put(date, row);
}
for (SaleResponse sale : sales) {
if (Boolean.TRUE.equals(sale.getIsRefund()) || sale.getSaleDate() == null) {
continue;
}
LocalDate date = sale.getSaleDate().toLocalDate();
DailySales row = daily.get(date);
if (row != null) {
row.setRevenue(row.getRevenue().add(sale.getTotalAmount() == null ? BigDecimal.ZERO : sale.getTotalAmount()));
row.setSalesCount(row.getSalesCount() + 1);
}
}
return new ArrayList<>(daily.values());
}
private List<TopProduct> buildTopProducts(List<SaleResponse> sales, int top) {
Map<Long, TopProduct> totals = new HashMap<>();
for (SaleResponse sale : sales) {
if (Boolean.TRUE.equals(sale.getIsRefund()) || sale.getItems() == null) {
continue;
}
for (SaleItemResponse item : sale.getItems()) {
if (item.getProdId() == null) {
continue;
}
TopProduct product = totals.computeIfAbsent(item.getProdId(), id -> {
TopProduct p = new TopProduct();
p.setProductId(id);
p.setProductName(item.getProductName());
p.setQuantitySold(0L);
p.setRevenue(BigDecimal.ZERO);
return p;
});
long quantity = item.getQuantity() == null ? 0 : item.getQuantity();
BigDecimal unitPrice = item.getUnitPrice() == null ? BigDecimal.ZERO : item.getUnitPrice();
product.setQuantitySold(product.getQuantitySold() + quantity);
product.setRevenue(product.getRevenue().add(unitPrice.multiply(BigDecimal.valueOf(quantity))));
}
}
return totals.values().stream()
.sorted((a, b) -> b.getRevenue().compareTo(a.getRevenue()))
.limit(top)
.toList();
}
private List<DashboardResponse.PaymentMethodData> buildPaymentMethods(List<SaleResponse> sales) {
Map<String, Long> totals = new TreeMap<>();
for (SaleResponse sale : sales) {
if (Boolean.TRUE.equals(sale.getIsRefund())) {
continue;
}
String method = sale.getPaymentMethod() == null || sale.getPaymentMethod().isBlank() ? "Unknown" : sale.getPaymentMethod();
totals.merge(method, 1L, Long::sum);
}
return totals.entrySet().stream().map(entry -> {
DashboardResponse.PaymentMethodData data = new DashboardResponse.PaymentMethodData();
data.setPaymentMethod(entry.getKey());
data.setCount(entry.getValue());
return data;
}).toList();
}
private void applyLineChartColor(LineChart<Number, Number> chart, String color) {
Platform.runLater(() -> {
for (XYChart.Series<Number, Number> series : chart.getData()) {

View File

@@ -20,6 +20,7 @@ import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialo
import org.example.petshopdesktop.util.ActivityLogger;
import java.util.List;
import java.util.Comparator;
import java.util.stream.Collectors;
public class AppointmentController {
@@ -32,6 +33,7 @@ public class AppointmentController {
@FXML private TableColumn<AppointmentDTO,String> colAppointmentDate;
@FXML private TableColumn<AppointmentDTO,String> colAppointmentTime;
@FXML private TableColumn<AppointmentDTO,String> colCustomerName;
@FXML private TableColumn<AppointmentDTO,String> colEmployeeName;
@FXML private TableColumn<AppointmentDTO,String> colAppointmentStatus;
@FXML private Button btnAdd;
@@ -45,7 +47,7 @@ public class AppointmentController {
@FXML
public void initialize(){
//Enable multiple selection
tvAppointments.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);
colAppointmentId.setCellValueFactory(new PropertyValueFactory<>("appointmentId"));
@@ -54,6 +56,7 @@ public class AppointmentController {
colAppointmentDate.setCellValueFactory(new PropertyValueFactory<>("appointmentDate"));
colAppointmentTime.setCellValueFactory(new PropertyValueFactory<>("appointmentTime"));
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
colAppointmentStatus.setCellValueFactory(new PropertyValueFactory<>("appointmentStatus"));
filtered = new FilteredList<>(appointments, a -> true);
@@ -63,7 +66,6 @@ public class AppointmentController {
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
}
//EventListener for DELETE key
tvAppointments.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
if (tvAppointments.getSelectionModel().getSelectedItem() != null) {
@@ -81,6 +83,7 @@ public class AppointmentController {
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null);
List<AppointmentDTO> appointmentDTOs = responses.stream()
.map(this::mapToAppointmentDTO)
.sorted(Comparator.comparing((AppointmentDTO a) -> a.getAppointmentDate() + "T" + a.getAppointmentTime()).reversed())
.collect(Collectors.toList());
Platform.runLater(() -> {
@@ -105,6 +108,7 @@ public class AppointmentController {
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(query);
List<AppointmentDTO> appointmentDTOs = responses.stream()
.map(this::mapToAppointmentDTO)
.sorted(Comparator.comparing((AppointmentDTO a) -> a.getAppointmentDate() + "T" + a.getAppointmentTime()).reversed())
.collect(Collectors.toList());
Platform.runLater(() -> {
@@ -143,11 +147,10 @@ public class AppointmentController {
@FXML
void btnDeleteClicked(ActionEvent event){
//get selected appointments
var selectedAppointments = tvAppointments.getSelectionModel().getSelectedItems();
if (selectedAppointments.isEmpty()) return;
//ask user to confirm
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete");
String message = selectedAppointments.size() == 1
@@ -157,7 +160,6 @@ public class AppointmentController {
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
java.util.Optional<ButtonType> result = question.showAndWait();
//if confirmed, start deletion
if (result.isPresent() && result.get() == ButtonType.OK) {
List<Long> ids = selectedAppointments.stream()
.map(a -> (long) a.getAppointmentId())
@@ -180,7 +182,6 @@ public class AppointmentController {
alert.showAndWait();
}
//refresh display
loadAppointments();
}
}
@@ -230,15 +231,16 @@ public class AppointmentController {
}
private AppointmentDTO mapToAppointmentDTO(AppointmentResponse response) {
Long petId = response.getPetIds() != null && !response.getPetIds().isEmpty() ? response.getPetIds().get(0) : null;
return new AppointmentDTO(
response.getAppointmentId().intValue(),
response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
response.getCustomerName(),
petId != null ? petId.intValue() : 0,
String.join(", ", response.getPetNames()),
response.getPetId() != null ? response.getPetId().intValue() : 0,
response.getPetName(),
response.getServiceId() != null ? response.getServiceId().intValue() : 0,
response.getServiceName(),
response.getEmployeeId() != null ? response.getEmployeeId().intValue() : 0,
response.getEmployeeName(),
response.getAppointmentDate().toString(),
response.getAppointmentTime().toString(),
response.getAppointmentStatus()

View File

@@ -123,20 +123,28 @@ public class ChatController {
@FXML
void btnSendClicked() {
if (selectedConversation == null) {
lblChatStatus.setText("Select a conversation");
return;
}
try {
if (selectedConversation == null) {
lblChatStatus.setText("Select a conversation");
return;
}
String content = txtMessage.getText() == null ? "" : txtMessage.getText().trim();
if (content.isEmpty()) {
return;
}
String content = txtMessage.getText() == null ? "" : txtMessage.getText().trim();
if (content.isEmpty()) {
return;
}
txtMessage.clear();
boolean sent = realtimeClient.sendMessage(selectedConversation.getId(), content);
if (!sent) {
sendMessageFallback(selectedConversation.getId(), content);
txtMessage.clear();
boolean sent = realtimeClient.sendMessage(selectedConversation.getId(), content);
if (!sent) {
sendMessageFallback(selectedConversation.getId(), content);
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ChatController.btnSendClicked",
e,
"Sending chat message");
lblChatStatus.setText("Chat send failed");
}
}
@@ -223,16 +231,30 @@ public class ChatController {
private void renderMessages(List<MessageResponse> messages) {
vbMessages.getChildren().clear();
for (MessageResponse message : messages) {
vbMessages.getChildren().add(createMessageBubble(message));
try {
vbMessages.getChildren().add(createMessageBubble(message));
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ChatController.renderMessages",
e,
"Rendering chat message");
}
}
scrollMessagesToBottom();
}
private void appendMessageIfSelected(MessageResponse message) {
upsertConversationForMessage(message);
if (selectedConversation != null && selectedConversation.getId().equals(message.getConversationId())) {
vbMessages.getChildren().add(createMessageBubble(message));
scrollMessagesToBottom();
try {
upsertConversationForMessage(message);
if (selectedConversation != null && selectedConversation.getId().equals(message.getConversationId())) {
vbMessages.getChildren().add(createMessageBubble(message));
scrollMessagesToBottom();
}
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"ChatController.appendMessageIfSelected",
e,
"Appending chat message");
}
}
@@ -284,15 +306,34 @@ public class ChatController {
Label author = new Label(resolveAuthorLabel(message));
author.setStyle("-fx-font-weight: bold; -fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
Label content = new Label(message.getContent());
content.setWrapText(true);
content.setStyle("-fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
String timestampText = message.getTimestamp() == null ? "" : TIME_FORMATTER.format(message.getTimestamp());
Label timestamp = new Label(timestampText);
timestamp.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#94a3b8") + "; -fx-font-size: 11px;");
VBox bubble = new VBox(4, author, content, timestamp);
VBox bubble = new VBox(4, author);
String contentText = message.getContent() == null ? "" : message.getContent();
if (!contentText.isBlank()) {
Label content = new Label(contentText);
content.setWrapText(true);
content.setStyle("-fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
bubble.getChildren().add(content);
}
if (message.getAttachmentUrl() != null && !message.getAttachmentUrl().isBlank()) {
String attachmentLabel = message.getAttachmentName();
if (attachmentLabel == null || attachmentLabel.isBlank()) {
attachmentLabel = "Attachment";
}
if (message.getAttachmentSizeBytes() != null && message.getAttachmentSizeBytes() > 0) {
attachmentLabel = attachmentLabel + " (" + formatSize(message.getAttachmentSizeBytes()) + ")";
}
Label attachment = new Label(attachmentLabel);
attachment.setWrapText(true);
attachment.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#0f766e") + "; -fx-underline: true;");
bubble.getChildren().add(attachment);
}
bubble.getChildren().add(timestamp);
bubble.setMaxWidth(420);
bubble.setStyle(mine
? "-fx-background-color: #0f766e; -fx-background-radius: 14; -fx-padding: 12;"
@@ -347,4 +388,17 @@ public class ChatController {
private void scrollMessagesToBottom() {
Platform.runLater(() -> spMessages.setVvalue(1.0));
}
private String formatSize(Long bytes) {
if (bytes == null || bytes <= 0) {
return "";
}
double size = bytes;
String[] units = {"B", "KB", "MB", "GB"};
int unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size = size / 1024;
unitIndex++;
}
return unitIndex == 0 ? String.format("%.0f %s", size, units[unitIndex]) : String.format("%.1f %s", size, units[unitIndex]);
}
}

View File

@@ -235,8 +235,8 @@ public class InventoryController {
response.getProdId() != null ? response.getProdId().intValue() : 0,
response.getProductName(),
response.getCategoryName() != null ? response.getCategoryName() : "",
0,
"N/A",
response.getStoreId() != null ? response.getStoreId().intValue() : 0,
response.getStoreName() != null ? response.getStoreName() : "",
response.getQuantity() != null ? response.getQuantity() : 0,
0
);

View File

@@ -358,6 +358,7 @@ public class MainLayoutController {
lblRole.setText("Leon's Petstore");
boolean isAdmin = session.isAdmin();
boolean canViewAnalytics = isAdmin || session.isStaff();
btnInventory.setVisible(isAdmin);
btnInventory.setManaged(isAdmin);
btnSuppliers.setVisible(isAdmin);
@@ -384,8 +385,8 @@ public class MainLayoutController {
}
if (btnAnalytics != null) {
btnAnalytics.setVisible(isAdmin);
btnAnalytics.setManaged(isAdmin);
btnAnalytics.setVisible(canViewAnalytics);
btnAnalytics.setManaged(canViewAnalytics);
}
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");

View File

@@ -15,6 +15,8 @@ import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.pet.PetResponse;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.PetApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogController;
import org.example.petshopdesktop.models.Pet;
@@ -61,9 +63,21 @@ public class PetController {
@FXML
private TableColumn<Pet, String> colPetStatus;
@FXML
private TableColumn<Pet, String> colCustomerName;
@FXML
private TableColumn<Pet, String> colStoreName;
@FXML
private TableView<Pet> tvPets;
@FXML
private ComboBox<String> cbSpeciesFilter;
@FXML
private ComboBox<String> cbStatusFilter;
@FXML
private TextField txtSearch;
@@ -148,8 +162,15 @@ public class PetController {
colPetAge.setCellValueFactory(new PropertyValueFactory<Pet,Integer>("petAge"));
colPetStatus.setCellValueFactory(new PropertyValueFactory<Pet,String>("petStatus"));
colPetPrice.setCellValueFactory(new PropertyValueFactory<Pet,Double>("petPrice"));
colCustomerName.setCellValueFactory(new PropertyValueFactory<Pet,String>("customerName"));
colStoreName.setCellValueFactory(new PropertyValueFactory<Pet,String>("storeName"));
configureImageColumn(colPetImage);
loadSpeciesFilter();
cbStatusFilter.setItems(FXCollections.observableArrayList("All Statuses", "Available", "Adopted", "Owned", "Pending"));
cbStatusFilter.getSelectionModel().selectFirst();
displayPets();
tvPets.getSelectionModel().selectedItemProperty().addListener(
@@ -159,9 +180,12 @@ public class PetController {
});
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredPet(newValue);
applyFilters();
});
cbSpeciesFilter.valueProperty().addListener((observable, oldValue, newValue) -> applyFilters());
cbStatusFilter.valueProperty().addListener((observable, oldValue, newValue) -> applyFilters());
//EventListener for DELETE key
tvPets.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
@@ -173,12 +197,14 @@ public class PetController {
}
private void displayFilteredPet(String filter) {
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
String species = selectedSpecies();
String status = selectedStatus();
if ((filter == null || filter.isEmpty()) && species == null && status == null){
displayPets();
} else {
new Thread(() -> {
try {
List<PetResponse> pets = PetApi.getInstance().listPets(filter);
List<PetResponse> pets = PetApi.getInstance().listPets(filter, species, status);
List<Pet> petList = pets.stream()
.map(this::mapToPet)
.collect(Collectors.toList());
@@ -203,7 +229,7 @@ public class PetController {
private void displayPets() {
new Thread(() -> {
try {
List<PetResponse> pets = PetApi.getInstance().listPets(null);
List<PetResponse> pets = PetApi.getInstance().listPets(null, selectedSpecies(), selectedStatus());
List<Pet> petList = pets.stream()
.map(this::mapToPet)
.collect(Collectors.toList());
@@ -224,6 +250,40 @@ public class PetController {
}).start();
}
private void applyFilters() {
displayFilteredPet(txtSearch.getText());
}
private void loadSpeciesFilter() {
new Thread(() -> {
try {
List<String> values = DropdownApi.getInstance().getPetSpecies().stream()
.map(DropdownOption::getLabel)
.collect(Collectors.toList());
values.add(0, "All Species");
Platform.runLater(() -> {
cbSpeciesFilter.setItems(FXCollections.observableArrayList(values));
cbSpeciesFilter.getSelectionModel().selectFirst();
});
} catch (Exception e) {
Platform.runLater(() -> ActivityLogger.getInstance().logException(
"PetController.loadSpeciesFilter",
e,
"Loading species filter options"));
}
}).start();
}
private String selectedSpecies() {
String value = cbSpeciesFilter.getValue();
return value == null || value.equals("All Species") ? null : value;
}
private String selectedStatus() {
String value = cbStatusFilter.getValue();
return value == null || value.equals("All Statuses") ? null : value;
}
private void openDialog(Pet pet, String mode){
//Get new view
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml"));
@@ -264,7 +324,7 @@ public class PetController {
}
private Pet mapToPet(PetResponse response) {
return new Pet(
Pet pet = new Pet(
response.getPetId().intValue(),
response.getPetName(),
response.getPetSpecies(),
@@ -274,6 +334,11 @@ public class PetController {
response.getPetPrice().doubleValue(),
response.getImageUrl()
);
pet.setCustomerName(response.getCustomerName());
pet.setStoreName(response.getStoreName());
pet.setCustomerId(response.getCustomerId() != null ? response.getCustomerId() : 0L);
pet.setStoreId(response.getStoreId() != null ? response.getStoreId() : 0L);
return pet;
}
private void configureImageColumn(TableColumn<Pet, String> column) {

View File

@@ -16,6 +16,8 @@ import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.ProductDTO;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.ProductDialogController;
import org.example.petshopdesktop.util.ActivityLogger;
@@ -62,6 +64,9 @@ public class ProductController {
@FXML
private TableView<ProductDTO> tvProducts;
@FXML
private ComboBox<DropdownOption> cbCategoryFilter;
@FXML
private TextField txtSearch;
@@ -87,6 +92,7 @@ public class ProductController {
colProductCategory.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("categoryName"));
colProductDesc.setCellValueFactory(new PropertyValueFactory<ProductDTO,String>("prodDesc"));
configureImageColumn(colProductImage);
loadCategoryFilter();
displayProduct();
@@ -100,9 +106,11 @@ public class ProductController {
//EventListener to search when text is changed on searchbar
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredProduct(newValue);
applyFilters();
});
cbCategoryFilter.valueProperty().addListener((observable, oldValue, newValue) -> applyFilters());
//EventListener for DELETE key press
tvProducts.setOnKeyPressed(event -> {
if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
@@ -120,7 +128,7 @@ public class ProductController {
private void displayProduct(){
new Thread(() -> {
try {
List<ProductResponse> products = ProductApi.getInstance().listProducts(null);
List<ProductResponse> products = ProductApi.getInstance().listProducts(null, selectedCategoryId());
List<ProductDTO> productDTOs = products.stream()
.map(this::mapToProductDTO)
.collect(Collectors.toList());
@@ -222,12 +230,12 @@ public class ProductController {
* @param filter word to filter table
*/
private void displayFilteredProduct(String filter){
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
if ((txtSearch.getText() == null || txtSearch.getText().isEmpty()) && selectedCategoryId() == null){
displayProduct();
} else {
new Thread(() -> {
try {
List<ProductResponse> products = ProductApi.getInstance().listProducts(filter);
List<ProductResponse> products = ProductApi.getInstance().listProducts(filter, selectedCategoryId());
List<ProductDTO> productDTOs = products.stream()
.map(this::mapToProductDTO)
.collect(Collectors.toList());
@@ -249,6 +257,37 @@ public class ProductController {
}
}
private void applyFilters() {
displayFilteredProduct(txtSearch.getText());
}
private void loadCategoryFilter() {
new Thread(() -> {
try {
List<DropdownOption> options = new ArrayList<>();
DropdownOption all = new DropdownOption();
all.setId(null);
all.setLabel("All Categories");
options.add(all);
options.addAll(DropdownApi.getInstance().getProductCategories());
Platform.runLater(() -> {
cbCategoryFilter.setItems(FXCollections.observableArrayList(options));
cbCategoryFilter.getSelectionModel().selectFirst();
});
} catch (Exception e) {
Platform.runLater(() -> ActivityLogger.getInstance().logException(
"ProductController.loadCategoryFilter",
e,
"Loading category filter options"));
}
}).start();
}
private Long selectedCategoryId() {
DropdownOption option = cbCategoryFilter.getValue();
return option == null ? null : option.getId();
}
/**
* Function to open the new Dialog for edit or adding
* depending on the mode given

View File

@@ -13,6 +13,7 @@ import org.example.petshopdesktop.api.endpoints.PurchaseOrderApi;
import org.example.petshopdesktop.util.ActivityLogger;
import java.util.List;
import java.util.Comparator;
import java.util.stream.Collectors;
public class PurchaseOrderController {
@@ -63,6 +64,7 @@ public class PurchaseOrderController {
List<PurchaseOrderResponse> responses = PurchaseOrderApi.getInstance().listPurchaseOrders(null);
List<PurchaseOrderDTO> dtos = responses.stream()
.map(this::mapToPurchaseOrderDTO)
.sorted(Comparator.comparing(PurchaseOrderDTO::getOrderDate).reversed())
.collect(Collectors.toList());
Platform.runLater(() -> {
@@ -118,4 +120,4 @@ public class PurchaseOrderController {
response.getOrderStatus()
);
}
}
}

View File

@@ -6,6 +6,8 @@ import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.application.Platform;
import javafx.scene.input.MouseButton;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
@@ -32,6 +34,7 @@ import org.example.petshopdesktop.api.dto.sale.SaleRequest;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.SaleCartItem;
import org.example.petshopdesktop.models.SaleDetail;
import org.example.petshopdesktop.models.SaleLineItem;
import org.example.petshopdesktop.util.ActivityLogger;
@@ -39,6 +42,7 @@ import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
@@ -128,6 +132,7 @@ public class SaleController {
private final ObservableList<SaleCartItem> cartItems = FXCollections.observableArrayList();
private final ObservableList<SaleLineItem> saleItems = FXCollections.observableArrayList();
private FilteredList<SaleLineItem> filteredSales;
private boolean saleSaveInProgress;
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@@ -165,6 +170,15 @@ public class SaleController {
filteredSales = new FilteredList<>(saleItems, s -> true);
tvSales.setItems(filteredSales);
tvSales.setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
SaleLineItem selected = tvSales.getSelectionModel().getSelectedItem();
if (selected != null) {
openSaleDetailDialog(selected.getSaleId());
}
}
});
txtSearch.textProperty().addListener((obs, oldVal, newVal) -> applySalesFilter(newVal));
}
@@ -177,22 +191,43 @@ public class SaleController {
updateCartTotal();
try {
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
ObservableList<Product> products = FXCollections.observableArrayList();
for (ProductResponse pr : productResponses) {
products.add(new Product(
pr.getProdId().intValue(),
pr.getProdName(),
pr.getProdPrice().doubleValue(),
0,
pr.getProdDesc()
));
setCreateSaleControlsDisabled(true);
Task<ObservableList<Product>> task = new Task<>() {
@Override
protected ObservableList<Product> call() throws Exception {
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
ObservableList<Product> products = FXCollections.observableArrayList();
for (ProductResponse pr : productResponses) {
products.add(new Product(
pr.getProdId().intValue(),
pr.getProdName(),
pr.getProdPrice().doubleValue(),
0,
pr.getProdDesc()
));
}
return products;
}
cbProduct.setItems(products);
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.setupCreateSale", e, "Loading products");
}
};
task.setOnSucceeded(event -> {
cbProduct.setItems(task.getValue());
setCreateSaleControlsDisabled(false);
});
task.setOnFailed(event -> {
Throwable e = task.getException();
ActivityLogger.getInstance().logException(
"SaleController.setupCreateSale",
e instanceof Exception ? (Exception) e : new RuntimeException(e),
"Loading products"
);
setCreateSaleControlsDisabled(false);
showError("Sales", "Could not load products. Check the backend connection and refresh the view.");
});
new Thread(task).start();
}
private void applyRoleMode() {
@@ -207,10 +242,13 @@ public class SaleController {
}
private void refreshSales(boolean showErrorDialog) {
btnRefresh.setDisable(true);
Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
@Override
protected List<SaleLineItem> call() throws Exception {
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, 1000, null);
List<SaleResponse> sales = SaleApi.getInstance().listAllSales(null);
sales.sort(Comparator.comparing(SaleResponse::getSaleDate, Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(SaleResponse::getSaleId, Comparator.nullsLast(Comparator.reverseOrder())));
List<SaleLineItem> lineItems = new ArrayList<>();
for (SaleResponse sale : sales) {
@@ -220,18 +258,23 @@ public class SaleController {
if (sale.getItems() != null && !sale.getItems().isEmpty()) {
for (SaleItemResponse item : sale.getItems()) {
boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
double lineTotal = unitPrice * item.getQuantity();
int quantity = item.getQuantity() != null ? item.getQuantity() : 0;
if (isRefund && quantity > 0) {
quantity = -quantity;
}
double lineTotal = unitPrice * quantity;
lineItems.add(new SaleLineItem(
sale.getSaleId().intValue(),
saleDate,
sale.getEmployeeName(),
item.getProductName(),
item.getQuantity(),
quantity,
unitPrice,
lineTotal,
sale.getPaymentMethod(),
sale.getIsRefund() != null && sale.getIsRefund()
isRefund
));
}
}
@@ -242,14 +285,14 @@ public class SaleController {
task.setOnSucceeded(event -> {
saleItems.setAll(task.getValue());
btnRefresh.setDisable(false);
});
task.setOnFailed(event -> {
Throwable e = task.getException();
ActivityLogger.getInstance().logException("SaleController.refreshSales", (Exception) e, "Loading sales");
if (showErrorDialog) {
showError("Sales", "Could not load sales: " + e.getMessage());
}
btnRefresh.setDisable(false);
showError("Sales", "Could not load sales: " + e.getMessage());
});
new Thread(task).start();
@@ -310,6 +353,9 @@ public class SaleController {
@FXML
void btnSaveSale(ActionEvent event) {
if (saleSaveInProgress) {
return;
}
if (UserSession.getInstance().isAdmin()) {
showError("Create Sale", "This action is restricted to staff.");
return;
@@ -332,36 +378,57 @@ public class SaleController {
return;
}
try {
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
List<SaleItemRequest> itemRequests = new ArrayList<>();
for (SaleCartItem cartItem : cartItems) {
SaleItemRequest itemRequest = new SaleItemRequest();
itemRequest.setProdId((long) cartItem.getProdId());
itemRequest.setQuantity(cartItem.getQuantity());
itemRequests.add(itemRequest);
List<SaleItemRequest> itemRequests = new ArrayList<>();
for (SaleCartItem cartItem : cartItems) {
SaleItemRequest itemRequest = new SaleItemRequest();
itemRequest.setProdId((long) cartItem.getProdId());
itemRequest.setQuantity(cartItem.getQuantity());
itemRequests.add(itemRequest);
}
request.setItems(itemRequests);
saleSaveInProgress = true;
setCreateSaleControlsDisabled(true);
btnRefund.setDisable(true);
Task<SaleResponse> task = new Task<>() {
@Override
protected SaleResponse call() throws Exception {
return SaleApi.getInstance().createSale(request);
}
request.setItems(itemRequests);
};
SaleResponse response = SaleApi.getInstance().createSale(request);
task.setOnSucceeded(evt -> {
saleSaveInProgress = false;
setCreateSaleControlsDisabled(false);
btnRefund.setDisable(false);
SaleResponse response = task.getValue();
showInfo("Sale saved", "Sale ID " + response.getSaleId() + " was created.");
cartItems.clear();
updateCartTotal();
refreshSales(true);
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", e, "Creating sale");
String errorMsg = e.getMessage();
});
task.setOnFailed(evt -> {
saleSaveInProgress = false;
setCreateSaleControlsDisabled(false);
btnRefund.setDisable(false);
Throwable e = task.getException();
Exception ex = e instanceof Exception ? (Exception) e : new RuntimeException(e);
ActivityLogger.getInstance().logException("SaleController.btnSaveSale", ex, "Creating sale");
String errorMsg = e != null ? e.getMessage() : null;
if (errorMsg != null && errorMsg.contains("Insufficient inventory")) {
showError("Create Sale", "Insufficient stock for one or more items.");
} else {
showError("Create Sale", errorMsg != null ? errorMsg : "Could not save the sale.");
}
}
});
new Thread(task).start();
}
@FXML
@@ -384,11 +451,13 @@ public class SaleController {
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setTitle("Process Refund");
dialog.setScene(new Scene(loader.load()));
var controller = loader.<org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController>getController();
if (selectedSale != null) {
loader.<org.example.petshopdesktop.controllers.dialogcontrollers.RefundDialogController>getController()
.prefillSale((long) selectedSale.getSaleId());
controller.prefillSale((long) selectedSale.getSaleId());
}
dialog.setResizable(false);
dialog.setMinWidth(860);
dialog.setMinHeight(680);
dialog.setResizable(true);
dialog.showAndWait();
refreshSales(true);
@@ -397,11 +466,87 @@ public class SaleController {
}
}
private void openSaleDetailDialog(int saleId) {
Task<SaleResponse> task = new Task<>() {
@Override
protected SaleResponse call() throws Exception {
return SaleApi.getInstance().getSale((long) saleId);
}
};
task.setOnSucceeded(event -> {
try {
SaleResponse sale = task.getValue();
FXMLLoader loader = new FXMLLoader(getClass().getResource(
"/org/example/petshopdesktop/dialogviews/sale-detail-dialog-view.fxml"));
Stage dialog = new Stage();
dialog.initOwner(tvSales.getScene().getWindow());
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setTitle("Sale Details");
dialog.setScene(new Scene(loader.load()));
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.SaleDetailDialogController) loader.getController();
controller.displaySaleDetails(mapToSaleDetail(sale));
dialog.setResizable(false);
dialog.showAndWait();
} catch (Exception e) {
ActivityLogger.getInstance().logException("SaleController.openSaleDetailDialog", e, "Opening sale detail dialog");
showError("Sale Details", "Could not open the sale details.");
}
});
task.setOnFailed(event -> {
Throwable e = task.getException();
ActivityLogger.getInstance().logException("SaleController.openSaleDetailDialog", (Exception) e, "Loading sale detail");
showError("Sale Details", "Could not open the sale details.");
});
new Thread(task).start();
}
private SaleDetail mapToSaleDetail(SaleResponse sale) {
ObservableList<SaleDetail.SaleDetailItem> items = FXCollections.observableArrayList();
if (sale.getItems() != null) {
boolean isRefund = sale.getIsRefund() != null && sale.getIsRefund();
for (SaleItemResponse item : sale.getItems()) {
double unitPrice = item.getUnitPrice() != null ? item.getUnitPrice().doubleValue() : 0.0;
int quantity = item.getQuantity() != null ? item.getQuantity() : 0;
if (isRefund && quantity > 0) {
quantity = -quantity;
}
items.add(new SaleDetail.SaleDetailItem(
item.getProdId() != null ? item.getProdId().intValue() : 0,
item.getProductName(),
quantity,
unitPrice,
unitPrice * quantity
));
}
}
return new SaleDetail(
sale.getSaleId().intValue(),
sale.getSaleDate(),
sale.getTotalAmount() != null ? sale.getTotalAmount().doubleValue() : 0.0,
sale.getPaymentMethod(),
sale.getEmployeeName(),
items
);
}
private void updateCartTotal() {
double total = cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum();
lblCartTotal.setText(currency.format(total));
}
private void setCreateSaleControlsDisabled(boolean disabled) {
cbProduct.setDisable(disabled);
spQuantity.setDisable(disabled);
btnAddToCart.setDisable(disabled);
btnRemoveSelected.setDisable(disabled);
cbPaymentMethod.setDisable(disabled);
btnClearCart.setDisable(disabled);
btnSaveSale.setDisable(disabled);
}
private void applySalesFilter(String filter) {
String f = filter == null ? "" : filter.trim().toLowerCase();
if (f.isEmpty()) {

View File

@@ -25,6 +25,7 @@ import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.Timestamp;
import java.time.ZoneId;
import java.util.List;
import java.util.Comparator;
import java.util.stream.Collectors;
public class StaffAccountsController {
@@ -161,6 +162,7 @@ public class StaffAccountsController {
List<EmployeeResponse> employees = EmployeeApi.getInstance().listEmployees(null);
List<StaffAccount> accounts = employees.stream()
.map(this::mapToStaffAccount)
.sorted(Comparator.comparing(StaffAccount::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder())))
.collect(Collectors.toList());
Platform.runLater(() -> {
@@ -184,10 +186,14 @@ public class StaffAccountsController {
long userId = employee.getUserId() != null ? employee.getUserId() : 0L;
long employeeId = employee.getEmployeeId() != null ? employee.getEmployeeId() : 0L;
String username = employee.getUsername();
String fullName = employee.getFullName() != null ? employee.getFullName() : "";
String[] names = splitFullName(fullName);
String firstName = names[0];
String lastName = names[1];
String firstName = employee.getFirstName() != null ? employee.getFirstName() : "";
String lastName = employee.getLastName() != null ? employee.getLastName() : "";
if (firstName.isBlank() && lastName.isBlank()) {
String fullName = employee.getFullName() != null ? employee.getFullName() : "";
String[] names = splitFullName(fullName);
firstName = names[0];
lastName = names[1];
}
String email = employee.getEmail() != null ? employee.getEmail() : "";
String phone = employee.getPhone() != null ? employee.getPhone() : "";
String role = employee.getRole() != null ? employee.getRole() : "STAFF";

View File

@@ -11,21 +11,23 @@ import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.util.ActivityLogger;
import java.time.LocalDate;
import java.util.List;
import java.util.Objects;
public class AdoptionDialogController {
//FXML elements
@FXML
private Button btnCancel;
@@ -38,6 +40,9 @@ public class AdoptionDialogController {
@FXML
private ComboBox<DropdownOption> cbCustomer;
@FXML
private ComboBox<DropdownOption> cbEmployee;
@FXML
private ComboBox<DropdownOption> cbPet;
@@ -50,10 +55,9 @@ public class AdoptionDialogController {
@FXML
private Label lblMode;
//Stores if the dialog view is in add/edit mode
private String mode = null;
private Adoption selectedAdoption = null;
//Adoption statuses
private ObservableList<String> statusList = FXCollections.observableArrayList(
"Pending", "Completed", "Cancelled"
);
@@ -62,14 +66,16 @@ public class AdoptionDialogController {
void initialize() {
cbAdoptionStatus.setItems(statusList);
cbEmployee.setPromptText("Select an employee");
new Thread(() -> {
try {
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
List<DropdownOption> pets = DropdownApi.getInstance().getAdoptionPets();
Platform.runLater(() -> {
if (pets != null) {
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
cbPet.setItems(petsObs);
applySelectedPet();
}
});
} catch (Exception e) {
@@ -83,6 +89,46 @@ public class AdoptionDialogController {
}
}).start();
new Thread(() -> {
try {
Long storeId = UserSession.getInstance().getStoreId();
List<DropdownOption> employees;
if (storeId != null && storeId > 0) {
employees = DropdownApi.getInstance().getStoreEmployees(storeId);
} else {
employees = DropdownApi.getInstance().getEmployees();
}
Platform.runLater(() -> {
cbEmployee.setItems(FXCollections.observableArrayList(employees));
applySelectedEmployee();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading employees for combo box");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Unable to load employees");
});
}
}).start();
cbEmployee.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbEmployee.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
new Thread(() -> {
try {
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
@@ -90,6 +136,7 @@ public class AdoptionDialogController {
if (customers != null) {
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
cbCustomer.setItems(customersObs);
applySelectedCustomer();
}
});
} catch (Exception e) {
@@ -129,6 +176,10 @@ public class AdoptionDialogController {
errorMsg += "Customer is required.\n";
}
if (cbEmployee.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Employee is required.\n";
}
if (dpAdoptionDate.getValue() == null) {
errorMsg += "Adoption Date is required.\n";
}
@@ -139,9 +190,16 @@ public class AdoptionDialogController {
if (errorMsg.isEmpty()) {
try {
Long storeId = UserSession.getInstance().getStoreId();
if (storeId == null || storeId <= 0) {
throw new IllegalStateException("Store is not set for this account");
}
AdoptionRequest request = new AdoptionRequest();
request.setPetId(cbPet.getSelectionModel().getSelectedItem().getId());
request.setCustomerId(cbCustomer.getSelectionModel().getSelectedItem().getId());
request.setEmployeeId(cbEmployee.getSelectionModel().getSelectedItem().getId());
request.setSourceStoreId(storeId);
request.setAdoptionDate(dpAdoptionDate.getValue());
request.setAdoptionStatus(cbAdoptionStatus.getValue());
@@ -179,7 +237,6 @@ public class AdoptionDialogController {
}
}
private void closeStage(MouseEvent mouseEvent) {
Node node = (Node) mouseEvent.getSource();
Stage stage = (Stage) node.getScene().getWindow();
@@ -188,21 +245,11 @@ public class AdoptionDialogController {
public void displayAdoptionDetails(Adoption adoption) {
if (adoption != null) {
selectedAdoption = adoption;
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
for (DropdownOption pet : cbPet.getItems()) {
if (pet.getLabel().equals(adoption.getPetName())) {
cbPet.getSelectionModel().select(pet);
break;
}
}
for (DropdownOption customer : cbCustomer.getItems()) {
if (customer.getLabel().equals(adoption.getCustomerName())) {
cbCustomer.getSelectionModel().select(customer);
break;
}
}
applySelectedPet();
applySelectedCustomer();
applySelectedEmployee();
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
try {
@@ -229,4 +276,46 @@ public class AdoptionDialogController {
lblMode.setText(mode + " Adoption");
lblAdoptionId.setVisible(mode.equals("Edit"));
}
private void applySelectedPet() {
if (selectedAdoption == null || selectedAdoption.getPetId() <= 0) {
return;
}
DropdownOption selected = findOptionById(cbPet.getItems(), (long) selectedAdoption.getPetId());
if (selected != null && !Objects.equals(cbPet.getValue(), selected)) {
cbPet.setValue(selected);
}
}
private void applySelectedCustomer() {
if (selectedAdoption == null || selectedAdoption.getCustomerId() <= 0) {
return;
}
DropdownOption selected = findOptionById(cbCustomer.getItems(), (long) selectedAdoption.getCustomerId());
if (selected != null && !Objects.equals(cbCustomer.getValue(), selected)) {
cbCustomer.setValue(selected);
}
}
private void applySelectedEmployee() {
if (selectedAdoption == null || selectedAdoption.getEmployeeId() <= 0) {
return;
}
DropdownOption selected = findOptionById(cbEmployee.getItems(), (long) selectedAdoption.getEmployeeId());
if (selected != null && !Objects.equals(cbEmployee.getValue(), selected)) {
cbEmployee.setValue(selected);
}
}
private DropdownOption findOptionById(List<DropdownOption> options, Long id) {
if (id == null || options == null) {
return null;
}
for (DropdownOption option : options) {
if (option.getId() != null && option.getId().equals(id)) {
return option;
}
}
return null;
}
}

View File

@@ -19,21 +19,19 @@ import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.util.ActivityLogger;
import java.time.LocalTime;
import java.util.Collections;
import java.time.LocalDate;
import java.util.List;
import java.util.Objects;
public class AppointmentDialogController {
// ============================
// FXML
// ============================
@FXML private Button btnCancel;
@FXML private Button btnSave;
@FXML private ComboBox<DropdownOption> cbService;
@FXML private ComboBox<DropdownOption> cbCustomer;
@FXML private ComboBox<DropdownOption> cbPet;
@FXML private ComboBox<DropdownOption> cbEmployee;
@FXML private ComboBox<Integer> cbHour;
@FXML private ComboBox<Integer> cbMinute;
@@ -44,74 +42,38 @@ public class AppointmentDialogController {
@FXML private Label lblAppointmentId;
@FXML private Label lblMode;
// ============================
// DATA
// ============================
private String mode = null; // Add | Edit
private String mode = null;
private AppointmentDTO selectedAppointment = null;
private Long pendingPetSelectionId = null;
private ObservableList<String> statusList =
FXCollections.observableArrayList(
"Booked", "Completed", "Cancelled"
"Booked", "Completed", "Cancelled", "Missed"
);
//
// MODE
//
public void setMode(String mode) {
this.mode = mode;
lblMode.setText(mode + " Appointment");
lblAppointmentId.setVisible(!mode.equals("Add"));
}
//
// INITIALIZE
//
@FXML
public void initialize() {
new Thread(() -> {
try {
List<DropdownOption> services = DropdownApi.getInstance().getServices();
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
List<DropdownOption> pets = DropdownApi.getInstance().getPets();
Platform.runLater(() -> {
if (services != null) {
cbService.setItems(FXCollections.observableArrayList(services));
}
if (customers != null) {
cbCustomer.setItems(FXCollections.observableArrayList(customers));
}
if (pets != null) {
cbPet.setItems(FXCollections.observableArrayList(pets));
}
syncSelectedAppointment();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.initialize",
e,
"Loading combo box data for services, customers, and pets");
e.printStackTrace();
});
}
}).start();
cbAppointmentStatus.setItems(statusList);
cbPet.setDisable(true);
cbEmployee.setPromptText("Select an employee");
cbPet.setPromptText("Select a customer first");
cbCustomer.setPromptText("Select a customer");
cbService.setPromptText("Select a service");
dpAppointmentDate.setValue(LocalDate.now().plusDays(1));
cbAppointmentStatus.setValue("Booked");
// Hours 9 AM - 5 PM
for (int i = 9; i <= 17; i++) {
cbHour.getItems().add(i);
}
cbMinute.getItems().addAll(0, 15, 30, 45);
// Show dropdown labels
cbService.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
@@ -157,18 +119,48 @@ public class AppointmentDialogController {
}
});
cbEmployee.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbEmployee.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbCustomer.valueProperty().addListener((obs, oldValue, newValue) -> {
Long customerId = newValue != null ? newValue.getId() : null;
cbPet.setValue(null);
cbPet.setItems(FXCollections.observableArrayList());
cbPet.setDisable(customerId == null);
if (customerId != null) {
cbPet.setPromptText("Loading customer pets...");
loadCustomerPets(customerId);
} else {
cbPet.setPromptText("Select a customer first");
pendingPetSelectionId = null;
}
});
btnSave.setOnMouseClicked(this::buttonSaveClicked);
btnCancel.setOnMouseClicked(this::closeStage);
}
//
// DISPLAY FOR EDIT
//
loadServices();
loadAppointmentCustomers();
loadEmployees();
}
public void displayAppointmentDetails(AppointmentDTO appt) {
selectedAppointment = appt;
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
pendingPetSelectionId = appt.getPetId() > 0 ? (long) appt.getPetId() : null;
try {
dpAppointmentDate.setValue(
@@ -194,28 +186,17 @@ public class AppointmentDialogController {
"Parsing appointment time");
}
cbService.getItems().forEach(s -> {
if (s.getId() != null && s.getId().longValue() == appt.getServiceId()) cbService.setValue(s);
});
cbCustomer.getItems().forEach(c -> {
if (c.getId() != null && c.getId().longValue() == appt.getCustomerId()) cbCustomer.setValue(c);
});
cbPet.getItems().forEach(p -> {
if (p.getId() != null && p.getId().longValue() == appt.getPetId()) cbPet.setValue(p);
});
applySelectedService();
applySelectedCustomer();
applySelectedEmployee();
}
//
// SAVE
//
private void buttonSaveClicked(MouseEvent e) {
if (cbService.getValue() == null ||
cbCustomer.getValue() == null ||
cbPet.getValue() == null ||
cbEmployee.getValue() == null ||
dpAppointmentDate.getValue() == null ||
cbHour.getValue() == null ||
cbMinute.getValue() == null ||
@@ -233,10 +214,11 @@ public class AppointmentDialogController {
}
AppointmentRequest request = new AppointmentRequest();
request.setPetIds(Collections.singletonList(cbPet.getValue().getId()));
request.setPetId(cbPet.getValue().getId());
request.setCustomerId(cbCustomer.getValue().getId());
request.setStoreId(storeId);
request.setServiceId(cbService.getValue().getId());
request.setEmployeeId(cbEmployee.getValue().getId());
request.setAppointmentDate(dpAppointmentDate.getValue());
request.setAppointmentTime(appointmentTime);
request.setAppointmentStatus(cbAppointmentStatus.getValue());
@@ -267,10 +249,6 @@ public class AppointmentDialogController {
}).start();
}
//
// UTIL
//
private void closeStage(MouseEvent e) {
Stage stage = (Stage) ((Node) e.getSource()).getScene().getWindow();
stage.close();
@@ -288,4 +266,156 @@ public class AppointmentDialogController {
displayAppointmentDetails(selectedAppointment);
}
}
private void applySelectedService() {
if (selectedAppointment == null || selectedAppointment.getServiceId() <= 0) {
return;
}
DropdownOption selected = findOptionById(cbService.getItems(), (long) selectedAppointment.getServiceId());
if (selected != null && !Objects.equals(cbService.getValue(), selected)) {
cbService.setValue(selected);
}
}
private void applySelectedCustomer() {
if (selectedAppointment == null || selectedAppointment.getCustomerId() <= 0) {
return;
}
DropdownOption selected = findOptionById(cbCustomer.getItems(), (long) selectedAppointment.getCustomerId());
if (selected != null && !Objects.equals(cbCustomer.getValue(), selected)) {
cbCustomer.setValue(selected);
}
}
private void applySelectedEmployee() {
if (selectedAppointment == null || selectedAppointment.getEmployeeId() <= 0) {
return;
}
DropdownOption selected = findOptionById(cbEmployee.getItems(), (long) selectedAppointment.getEmployeeId());
if (selected != null && !Objects.equals(cbEmployee.getValue(), selected)) {
cbEmployee.setValue(selected);
}
}
private DropdownOption findOptionById(List<DropdownOption> options, Long id) {
if (id == null || options == null) {
return null;
}
for (DropdownOption option : options) {
if (option.getId() != null && option.getId().equals(id)) {
return option;
}
}
return null;
}
private void loadCustomerPets(Long customerId) {
new Thread(() -> {
try {
List<DropdownOption> pets = DropdownApi.getInstance().getCustomerPets(customerId);
Platform.runLater(() -> {
cbPet.setItems(FXCollections.observableArrayList(pets));
cbPet.setDisable(pets == null || pets.isEmpty());
cbPet.setPromptText(pets == null || pets.isEmpty() ? "No pets for selected customer" : "Select a pet");
if (pendingPetSelectionId != null) {
for (DropdownOption pet : cbPet.getItems()) {
if (pet.getId() != null && pet.getId().equals(pendingPetSelectionId)) {
cbPet.setValue(pet);
break;
}
}
pendingPetSelectionId = null;
}
});
} catch (Exception ex) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.loadCustomerPets",
ex,
"Loading customer pets for appointment dialog");
cbPet.setDisable(true);
cbPet.setPromptText("Unable to load pets");
showError("Error loading pets for selected customer");
});
}
}).start();
}
private void loadServices() {
new Thread(() -> {
try {
List<DropdownOption> services = DropdownApi.getInstance().getServices();
Platform.runLater(() -> {
cbService.setItems(FXCollections.observableArrayList(services));
applySelectedService();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.loadServices",
e,
"Loading services for appointment dialog");
cbService.setDisable(true);
cbService.setPromptText("Unable to load services");
});
}
}).start();
}
private void loadAppointmentCustomers() {
new Thread(() -> {
try {
List<DropdownOption> customers = DropdownApi.getInstance().getAppointmentCustomers();
Platform.runLater(() -> {
cbCustomer.setItems(FXCollections.observableArrayList(customers));
boolean hasCustomers = customers != null && !customers.isEmpty();
cbCustomer.setDisable(!hasCustomers);
cbPet.setDisable(true);
cbPet.setItems(FXCollections.observableArrayList());
cbCustomer.setPromptText(hasCustomers ? "Select a customer" : "No customers with pets yet");
cbPet.setPromptText(hasCustomers ? "Select a customer first" : "No customer pets available");
applySelectedCustomer();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.loadAppointmentCustomers",
e,
"Loading appointment customers for appointment dialog");
cbCustomer.setDisable(true);
cbPet.setDisable(true);
cbCustomer.setPromptText("Unable to load customers");
cbPet.setPromptText("Unable to load pets");
});
}
}).start();
}
private void loadEmployees() {
new Thread(() -> {
try {
Long storeId = UserSession.getInstance().getStoreId();
List<DropdownOption> employees;
if (storeId != null && storeId > 0) {
employees = DropdownApi.getInstance().getStoreEmployees(storeId);
} else {
employees = DropdownApi.getInstance().getEmployees();
}
Platform.runLater(() -> {
cbEmployee.setItems(FXCollections.observableArrayList(employees));
applySelectedEmployee();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.loadEmployees",
e,
"Loading employees for appointment dialog");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Unable to load employees");
});
}
}).start();
}
}

View File

@@ -20,6 +20,7 @@ import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import org.example.petshopdesktop.api.endpoints.InventoryApi;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.util.ActivityLogger;
@@ -127,6 +128,10 @@ public class InventoryDialogController {
try {
InventoryRequest request = new InventoryRequest();
Product selectedProduct = cbProduct.getSelectionModel().getSelectedItem();
Long storeId = UserSession.getInstance().getStoreId();
if (storeId == null || storeId <= 0) {
throw new IllegalStateException("Store is not set for this account");
}
request.setProdId((long) selectedProduct.getProdId());
int quantity;
try {
@@ -135,6 +140,7 @@ public class InventoryDialogController {
throw new IllegalArgumentException("Invalid quantity format");
}
request.setQuantity(quantity);
request.setStoreId(storeId);
if (mode.equals("Add")) {
InventoryApi.getInstance().createInventory(request);
@@ -206,4 +212,4 @@ public class InventoryDialogController {
lblMode.setText(mode + " Inventory");
lblInventoryId.setVisible(mode.equals("Edit"));
}
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
@@ -10,8 +11,10 @@ import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.dto.pet.PetRequest;
import org.example.petshopdesktop.api.dto.pet.PetResponse;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.PetApi;
import org.example.petshopdesktop.models.Pet;
import org.example.petshopdesktop.util.ActivityLogger;
@@ -20,6 +23,7 @@ import org.example.petshopdesktop.util.FilePickerSupport;
import java.io.File;
import java.math.BigDecimal;
import java.util.List;
public class PetDialogController {
@@ -38,6 +42,12 @@ public class PetDialogController {
@FXML
private ComboBox<String> cbPetStatus;
@FXML
private ComboBox<DropdownOption> cbCustomer;
@FXML
private ComboBox<DropdownOption> cbStore;
@FXML
private Label lblMode;
@@ -70,16 +80,54 @@ public class PetDialogController {
private String currentImageUrl;
private boolean removeImageRequested;
private Long pendingCustomerId = null;
private Long pendingStoreId = null;
private ObservableList<String> statusList = FXCollections.observableArrayList(
"Available", "Adopted"
"Available", "Adopted", "Owned"
);
@FXML
void initialize() {
cbPetStatus.setItems(statusList); //set status combobox
cbPetStatus.setItems(statusList);
cbCustomer.setCellFactory(param -> new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
cbCustomer.setButtonCell(new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
cbStore.setCellFactory(param -> new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
cbStore.setButtonCell(new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
cbCustomer.setVisible(false);
cbStore.setVisible(false);
cbPetStatus.valueProperty().addListener((obs, oldVal, newVal) -> {
boolean isOwned = "Owned".equalsIgnoreCase(newVal);
boolean isAvailable = "Available".equalsIgnoreCase(newVal) || "Unadopted".equalsIgnoreCase(newVal);
cbCustomer.setVisible(isOwned);
cbStore.setVisible(isAvailable);
});
//Set up mouse handlers for buttons
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
@@ -97,6 +145,9 @@ public class PetDialogController {
btnChangeImage.setOnMouseClicked(mouseEvent -> handleChangeImage());
btnRemoveImage.setOnMouseClicked(mouseEvent -> handleRemoveImage());
refreshImagePreview();
loadCustomers();
loadStores();
}
private void buttonSaveClicked(MouseEvent mouseEvent) {
@@ -111,6 +162,10 @@ public class PetDialogController {
if (cbPetStatus.getSelectionModel().getSelectedItem() == null){
errorMsg += "Status is required";
}
String selectedStatus = cbPetStatus.getValue();
if ("Owned".equalsIgnoreCase(selectedStatus) && cbCustomer.getValue() == null) {
errorMsg += "Customer is required for Owned status\n";
}
//Check validation (length size)
errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50);
@@ -184,9 +239,81 @@ public class PetDialogController {
}
request.setPetAge(age);
String status = cbPetStatus.getValue();
if ("Owned".equalsIgnoreCase(status) && cbCustomer.getValue() != null) {
request.setCustomerId(cbCustomer.getValue().getId());
}
if (("Available".equalsIgnoreCase(status) || "Unadopted".equalsIgnoreCase(status)) && cbStore.getValue() != null) {
request.setStoreId(cbStore.getValue().getId());
}
return request;
}
private void loadCustomers() {
new Thread(() -> {
try {
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
Platform.runLater(() -> {
cbCustomer.setItems(FXCollections.observableArrayList(customers));
applySelectedCustomer();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"PetDialogController.loadCustomers", e, "Loading customers");
cbCustomer.setDisable(true);
cbCustomer.setPromptText("Unable to load customers");
});
}
}).start();
}
private void loadStores() {
new Thread(() -> {
try {
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
Platform.runLater(() -> {
cbStore.setItems(FXCollections.observableArrayList(stores));
applySelectedStore();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"PetDialogController.loadStores", e, "Loading stores");
cbStore.setDisable(true);
cbStore.setPromptText("Unable to load stores");
});
}
}).start();
}
private void applySelectedCustomer() {
if (pendingCustomerId == null) return;
DropdownOption selected = findOptionById(cbCustomer.getItems(), pendingCustomerId);
if (selected != null) {
cbCustomer.setValue(selected);
pendingCustomerId = null;
}
}
private void applySelectedStore() {
if (pendingStoreId == null) return;
DropdownOption selected = findOptionById(cbStore.getItems(), pendingStoreId);
if (selected != null) {
cbStore.setValue(selected);
pendingStoreId = null;
}
}
private DropdownOption findOptionById(List<DropdownOption> options, Long id) {
if (id == null || options == null) return null;
for (DropdownOption option : options) {
if (option.getId() != null && option.getId().equals(id)) return option;
}
return null;
}
private void closeStage(MouseEvent mouseEvent) {
Node node = (Node) mouseEvent.getSource();
Stage stage = (Stage) node.getScene().getWindow();
@@ -206,14 +333,14 @@ public class PetDialogController {
removeImageRequested = false;
refreshImagePreview();
//get the right combobox selection
pendingCustomerId = pet.getCustomerId() > 0 ? pet.getCustomerId() : null;
pendingStoreId = pet.getStoreId() > 0 ? pet.getStoreId() : null;
for (String status : cbPetStatus.getItems()) {
if(status.equals(pet.getPetStatus())){
cbPetStatus.getSelectionModel().select(status);
}
}
}
}

View File

@@ -89,7 +89,7 @@ public class ProductDialogController {
//Set up combobox for selecting category
try {
List<DropdownOption> categories = DropdownApi.getInstance().getCategories();
List<DropdownOption> categories = DropdownApi.getInstance().getProductCategories();
if (categories != null) {
ObservableList<DropdownOption> categoriesObs = FXCollections.observableArrayList(categories);
cbProdCategory.setItems(categoriesObs);

View File

@@ -4,8 +4,12 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.concurrent.Task;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.sale.SaleItemRequest;
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
@@ -90,6 +94,7 @@ public class RefundDialogController {
private final ObservableList<SaleItemResponse> originalItems = FXCollections.observableArrayList();
private final ObservableList<RefundItem> refundItems = FXCollections.observableArrayList();
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
private boolean refundInProgress;
@FXML
public void initialize() {
@@ -100,17 +105,19 @@ public class RefundDialogController {
}
private void setupTables() {
colOriginalProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
colOriginalQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
colOriginalUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
colOriginalTotal.setCellValueFactory(new PropertyValueFactory<>("lineTotal"));
tvOriginalItems.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
colOriginalProduct.setCellValueFactory(cell -> new ReadOnlyStringWrapper(cell.getValue().getProductName()));
colOriginalQuantity.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getQuantity()));
colOriginalUnitPrice.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getUnitPrice()));
colOriginalTotal.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getLineTotal()));
tvOriginalItems.setItems(originalItems);
tvOriginalItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
colRefundProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
colRefundQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
colRefundUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
colRefundTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
tvRefundItems.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
colRefundProduct.setCellValueFactory(cell -> new ReadOnlyStringWrapper(cell.getValue().getProductName()));
colRefundQuantity.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getQuantity()));
colRefundUnitPrice.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getUnitPrice()));
colRefundTotal.setCellValueFactory(cell -> new ReadOnlyObjectWrapper<>(cell.getValue().getTotal()));
tvRefundItems.setItems(refundItems);
tvRefundItems.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
}
@@ -125,12 +132,13 @@ public class RefundDialogController {
return;
}
txtSaleId.setText(String.valueOf(saleId));
loadSale();
Platform.runLater(this::loadSale);
}
private void loadSale() {
String saleIdText = txtSaleId.getText().trim();
if (saleIdText.isEmpty()) {
clearLoadedSale();
showError("Load Sale", "Enter a transaction ID.");
return;
}
@@ -139,22 +147,36 @@ public class RefundDialogController {
try {
saleId = Long.parseLong(saleIdText);
} catch (NumberFormatException e) {
clearLoadedSale();
showError("Load Sale", "Invalid transaction ID.");
return;
}
try {
List<SaleResponse> allSales = SaleApi.getInstance().listSales(0, 1000, null);
currentSale = SaleApi.getInstance().getSale(saleId);
if (Boolean.TRUE.equals(currentSale.getIsRefund())) {
clearLoadedSale();
showError("Load Sale", "Select an original sale, not a refund record.");
return;
}
List<SaleResponse> previousRefunds = allSales.stream()
.filter(s -> Boolean.TRUE.equals(s.getIsRefund()) && saleId.equals(s.getOriginalSaleId()))
.collect(Collectors.toList());
setLoadingState(true, "Loading sale...");
clearLoadedSale();
Task<LoadedSaleData> task = new Task<>() {
@Override
protected LoadedSaleData call() throws Exception {
List<SaleResponse> allSales = SaleApi.getInstance().listAllSales(null);
SaleResponse sale = SaleApi.getInstance().getSale(saleId);
if (Boolean.TRUE.equals(sale.getIsRefund())) {
throw new IllegalStateException("Select an original sale, not a refund record.");
}
List<SaleResponse> previousRefunds = allSales.stream()
.filter(s -> Boolean.TRUE.equals(s.getIsRefund()) && saleId.equals(s.getOriginalSaleId()))
.collect(Collectors.toList());
List<SaleItemResponse> refundableItems = buildRefundableItems(sale, previousRefunds);
if (refundableItems.isEmpty()) {
throw new IllegalStateException("This sale has no remaining refundable items.");
}
return new LoadedSaleData(sale, refundableItems);
}
};
task.setOnSucceeded(event -> {
LoadedSaleData loaded = task.getValue();
currentSale = loaded.sale();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String saleInfo = String.format("Sale Date: %s | Employee: %s | Original Total: %s | Payment: %s",
currentSale.getSaleDate().format(formatter),
@@ -162,26 +184,25 @@ public class RefundDialogController {
currency.format(currentSale.getTotalAmount()),
currentSale.getPaymentMethod());
lblSaleInfo.setText(saleInfo);
List<SaleItemResponse> refundableItems = buildRefundableItems(currentSale, previousRefunds);
if (refundableItems.isEmpty()) {
showError("Load Sale", "This sale has no remaining refundable items.");
return;
}
baseOriginalItems.clear();
baseOriginalItems.addAll(copySaleItems(refundableItems));
originalItems.setAll(copySaleItems(refundableItems));
baseOriginalItems.addAll(copySaleItems(loaded.refundableItems()));
originalItems.setAll(copySaleItems(loaded.refundableItems()));
cbPaymentMethod.getSelectionModel().select(currentSale.getPaymentMethod());
refundItems.clear();
updateOriginalItemAvailability();
updateRefundTotal();
setLoadingState(false, saleInfo);
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", e, "Loading sale");
task.setOnFailed(event -> {
clearLoadedSale();
Throwable e = task.getException();
ActivityLogger.getInstance().logException("RefundDialogController.btnLoadSaleClicked", (Exception) e, "Loading sale");
setLoadingState(false, "");
showError("Load Sale", e.getMessage() != null ? e.getMessage() : "Could not load sale.");
}
});
new Thread(task).start();
}
@FXML
@@ -248,6 +269,9 @@ public class RefundDialogController {
@FXML
void btnProcessRefundClicked(ActionEvent event) {
if (refundInProgress) {
return;
}
if (currentSale == null) {
showError("Process Refund", "Load a sale first.");
return;
@@ -280,36 +304,53 @@ public class RefundDialogController {
return;
}
try {
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
request.setIsRefund(true);
request.setOriginalSaleId(currentSale.getSaleId());
SaleRequest request = new SaleRequest();
request.setStoreId(storeId);
request.setPaymentMethod(payment);
request.setIsRefund(true);
request.setOriginalSaleId(currentSale.getSaleId());
List<SaleItemRequest> items = new ArrayList<>();
for (RefundItem item : refundItems) {
SaleItemRequest saleItem = new SaleItemRequest();
saleItem.setProdId((long) item.getProdId());
saleItem.setQuantity(-item.getQuantity());
items.add(saleItem);
List<SaleItemRequest> items = new ArrayList<>();
for (RefundItem item : refundItems) {
SaleItemRequest saleItem = new SaleItemRequest();
saleItem.setProdId((long) item.getProdId());
saleItem.setQuantity(-item.getQuantity());
items.add(saleItem);
}
request.setItems(items);
refundInProgress = true;
setLoadingState(true, lblSaleInfo.getText());
Task<SaleResponse> task = new Task<>() {
@Override
protected SaleResponse call() throws Exception {
return SaleApi.getInstance().createSale(request);
}
request.setItems(items);
SaleResponse refundResponse = SaleApi.getInstance().createSale(request);
};
task.setOnSucceeded(evt -> {
refundInProgress = false;
setLoadingState(false, lblSaleInfo.getText());
SaleResponse refundResponse = task.getValue();
Alert success = new Alert(Alert.AlertType.INFORMATION);
success.setTitle("Refund Processed");
success.setHeaderText(null);
success.setContentText("Refund ID " + refundResponse.getSaleId() + " was created successfully.");
success.showAndWait();
closeDialog();
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", e, "Processing refund");
showError("Process Refund", e.getMessage() != null ? e.getMessage() : "Could not process refund.");
}
task.setOnFailed(evt -> {
refundInProgress = false;
setLoadingState(false, lblSaleInfo.getText());
Throwable e = task.getException();
Exception ex = e instanceof Exception ? (Exception) e : new RuntimeException(e);
ActivityLogger.getInstance().logException("RefundDialogController.btnProcessRefundClicked", ex, "Processing refund");
showError("Process Refund", e != null && e.getMessage() != null ? e.getMessage() : "Could not process refund.");
});
new Thread(task).start();
}
@FXML
@@ -326,6 +367,18 @@ public class RefundDialogController {
updateRefundTotal();
}
private void setLoadingState(boolean loading, String message) {
btnLoadSale.setDisable(loading);
btnAddToRefund.setDisable(loading);
btnRemoveFromRefund.setDisable(loading);
btnProcessRefund.setDisable(loading);
txtSaleId.setDisable(loading);
cbPaymentMethod.setDisable(loading);
if (loading) {
lblSaleInfo.setText(message);
}
}
private void addOrMergeRefundItem(SaleItemResponse selected, int quantity) {
for (int i = 0; i < refundItems.size(); i++) {
RefundItem existing = refundItems.get(i);
@@ -486,4 +539,7 @@ public class RefundDialogController {
return quantity * unitPrice;
}
}
private record LoadedSaleData(SaleResponse sale, List<SaleItemResponse> refundableItems) {
}
}

View File

@@ -0,0 +1,54 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import org.example.petshopdesktop.models.SaleDetail;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class SaleDetailDialogController {
@FXML private Label lblSaleId;
@FXML private Label lblSaleDate;
@FXML private Label lblEmployee;
@FXML private Label lblPayment;
@FXML private Label lblTotal;
@FXML private TableView<SaleDetail.SaleDetailItem> tvItems;
@FXML private TableColumn<SaleDetail.SaleDetailItem, String> colProduct;
@FXML private TableColumn<SaleDetail.SaleDetailItem, Integer> colQuantity;
@FXML private TableColumn<SaleDetail.SaleDetailItem, Double> colUnitPrice;
@FXML private TableColumn<SaleDetail.SaleDetailItem, Double> colLineTotal;
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@FXML
public void initialize() {
tvItems.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
colProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
colQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
colUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
colLineTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
}
public void displaySaleDetails(SaleDetail sale) {
lblSaleId.setText(String.valueOf(sale.getSaleId()));
lblSaleDate.setText(sale.getSaleDate() != null ? sale.getSaleDate().format(DATE_FORMATTER) : "");
lblEmployee.setText(sale.getEmployeeName() != null ? sale.getEmployeeName() : "");
lblPayment.setText(sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "");
lblTotal.setText(currency.format(sale.getTotalAmount()));
tvItems.setItems(sale.getItems());
}
@FXML
void btnCloseClicked() {
Stage stage = (Stage) tvItems.getScene().getWindow();
stage.close();
}
}

View File

@@ -11,6 +11,7 @@ import javafx.stage.Stage;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.models.StaffAccount;
import org.example.petshopdesktop.util.ActivityLogger;
@@ -104,14 +105,18 @@ public class StaffEditDialogController {
new Thread(() -> {
try {
Long storeId = UserSession.getInstance().getStoreId();
EmployeeRequest request = new EmployeeRequest();
request.setUsername(username);
request.setPassword(password.isEmpty() ? null : password);
request.setFirstName(firstName);
request.setLastName(lastName);
request.setFullName(firstName + " " + lastName);
request.setEmail(email);
request.setPhone(phone);
request.setRole(staffAccount.getRole());
request.setStaffRole("Staff");
request.setPrimaryStoreId(storeId);
request.setActive(staffAccount.isActive());
EmployeeApi.getInstance().updateEmployee(staffAccount.getEmployeeId(), request);

View File

@@ -11,6 +11,7 @@ import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.util.ActivityLogger;
@@ -89,14 +90,18 @@ public class StaffRegisterDialogController {
new Thread(() -> {
try {
Long storeId = UserSession.getInstance().getStoreId();
EmployeeRequest request = new EmployeeRequest();
request.setUsername(username);
request.setPassword(password);
request.setFirstName(firstName);
request.setLastName(lastName);
request.setFullName(firstName + " " + lastName);
request.setEmail(email);
request.setPhone(phone);
request.setRole("STAFF");
request.setStaffRole("Staff");
request.setPrimaryStoreId(storeId);
request.setActive(true);
EmployeeApi.getInstance().createEmployee(request);

View File

@@ -8,18 +8,22 @@ public class Adoption {
private SimpleIntegerProperty adoptionId;
private SimpleIntegerProperty petId;
private SimpleIntegerProperty customerId;
private SimpleIntegerProperty employeeId;
private SimpleStringProperty petName;
private SimpleStringProperty customerName;
private SimpleStringProperty employeeName;
private SimpleStringProperty adoptionDate;
private SimpleDoubleProperty adoptionFee;
private SimpleStringProperty adoptionStatus;
public Adoption(int adoptionId, int petId, int customerId, String petName, String customerName, String adoptionDate, double adoptionFee, String adoptionStatus) {
public Adoption(int adoptionId, int petId, int customerId, int employeeId, String petName, String customerName, String employeeName, String adoptionDate, double adoptionFee, String adoptionStatus) {
this.adoptionId = new SimpleIntegerProperty(adoptionId);
this.petId = new SimpleIntegerProperty(petId);
this.customerId = new SimpleIntegerProperty(customerId);
this.employeeId = new SimpleIntegerProperty(employeeId);
this.petName = new SimpleStringProperty(petName);
this.customerName = new SimpleStringProperty(customerName);
this.employeeName = new SimpleStringProperty(employeeName);
this.adoptionDate = new SimpleStringProperty(adoptionDate);
this.adoptionFee = new SimpleDoubleProperty(adoptionFee);
this.adoptionStatus = new SimpleStringProperty(adoptionStatus);
@@ -43,6 +47,12 @@ public class Adoption {
public SimpleIntegerProperty customerIdProperty() { return customerId; }
public int getEmployeeId() { return employeeId.get(); }
public void setEmployeeId(int employeeId) { this.employeeId.set(employeeId); }
public SimpleIntegerProperty employeeIdProperty() { return employeeId; }
public String getPetName() { return petName.get(); }
public void setPetName(String petName) { this.petName.set(petName); }
@@ -55,6 +65,12 @@ public class Adoption {
public SimpleStringProperty customerNameProperty() { return customerName; }
public String getEmployeeName() { return employeeName.get(); }
public void setEmployeeName(String employeeName) { this.employeeName.set(employeeName); }
public SimpleStringProperty employeeNameProperty() { return employeeName; }
public String getAdoptionDate() { return adoptionDate.get(); }
public void setAdoptionDate(String adoptionDate) { this.adoptionDate.set(adoptionDate); }

View File

@@ -13,6 +13,10 @@ public class Pet {
private SimpleStringProperty petStatus;
private SimpleDoubleProperty petPrice;
private SimpleStringProperty imageUrl;
private SimpleStringProperty customerName = new SimpleStringProperty("");
private SimpleStringProperty storeName = new SimpleStringProperty("");
private long customerId = 0L;
private long storeId = 0L;
public Pet(int petId, String petName, String petSpecies, String petBreed, int petAge, String petStatus, double petPrice, String imageUrl) {
this.petId = new SimpleIntegerProperty(petId);
@@ -120,4 +124,44 @@ public class Pet {
public SimpleStringProperty imageUrlProperty() {
return imageUrl;
}
public String getCustomerName() {
return customerName.get();
}
public void setCustomerName(String customerName) {
this.customerName.set(customerName != null ? customerName : "");
}
public SimpleStringProperty customerNameProperty() {
return customerName;
}
public String getStoreName() {
return storeName.get();
}
public void setStoreName(String storeName) {
this.storeName.set(storeName != null ? storeName : "");
}
public SimpleStringProperty storeNameProperty() {
return storeName;
}
public long getCustomerId() {
return customerId;
}
public void setCustomerId(long customerId) {
this.customerId = customerId;
}
public long getStoreId() {
return storeId;
}
public void setStoreId(long storeId) {
this.storeId = storeId;
}
}

View File

@@ -73,6 +73,7 @@
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0">
@@ -131,6 +132,20 @@
</ComboBox>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2">
<children>
<Label text="Employee:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbEmployee" prefHeight="29.0" prefWidth="336.0" promptText="Select Employee" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
</children>
<VBox.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />

View File

@@ -83,6 +83,7 @@
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0">
@@ -190,9 +191,9 @@
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2">
<children>
<Label text="Pet:" textFill="#2c3e50">
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2">
<children>
<Label text="Pet:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
@@ -201,9 +202,23 @@
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
</ComboBox>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="3">
<children>
<Label text="Employee:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbEmployee" prefHeight="29.0" prefWidth="336.0" promptText="Select Employee" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
</children>
<VBox.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />

View File

@@ -13,7 +13,7 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="560.0" prefWidth="790.0" 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.dialogcontrollers.PetDialogController">
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="660.0" prefWidth="790.0" 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.dialogcontrollers.PetDialogController">
<children>
<HBox alignment="CENTER_LEFT" prefHeight="79.0" prefWidth="727.0" spacing="20.0" style="-fx-background-color: #2C3E50; -fx-background-radius: 14;">
<children>
@@ -153,6 +153,34 @@
</TextField>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="3">
<children>
<Label text="Customer:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbCustomer" prefHeight="29.0" prefWidth="336.0" promptText="Select Customer" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="3">
<children>
<Label text="Store:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbStore" prefHeight="29.0" prefWidth="336.0" promptText="Select Store" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
</children>
<VBox.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />

View File

@@ -13,7 +13,7 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="900.0" 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.dialogcontrollers.RefundDialogController">
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="720.0" prefWidth="920.0" spacing="22.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.dialogcontrollers.RefundDialogController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
@@ -84,14 +84,14 @@
</children>
</VBox>
<VBox spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #FF6b6b; -fx-border-radius: 14;" VBox.vgrow="ALWAYS">
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</padding>
<children>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<children>
<Label text="Original Items" textFill="#2c3e50">
<VBox spacing="18.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #FF6b6b; -fx-border-radius: 14;" VBox.vgrow="ALWAYS">
<padding>
<Insets bottom="18.0" left="18.0" right="18.0" top="18.0" />
</padding>
<children>
<HBox alignment="CENTER_LEFT" spacing="14.0">
<children>
<Label text="Original Items" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
@@ -107,20 +107,20 @@
</Button>
</children>
</HBox>
<TableView fx:id="tvOriginalItems" prefHeight="150.0" style="-fx-background-color: white; -fx-background-radius: 10;">
<columns>
<TableColumn fx:id="colOriginalProduct" prefWidth="350.0" text="Product Name" />
<TableColumn fx:id="colOriginalQuantity" prefWidth="120.0" text="Quantity" />
<TableColumn fx:id="colOriginalUnitPrice" prefWidth="150.0" text="Unit Price" />
<TableColumn fx:id="colOriginalTotal" prefWidth="150.0" text="Total" />
</columns>
</TableView>
<TableView fx:id="tvOriginalItems" prefHeight="190.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #E8EBED; -fx-border-radius: 10;">
<columns>
<TableColumn fx:id="colOriginalProduct" prefWidth="430.0" text="Product Name" />
<TableColumn fx:id="colOriginalQuantity" prefWidth="140.0" text="Quantity" />
<TableColumn fx:id="colOriginalUnitPrice" prefWidth="170.0" text="Unit Price" />
<TableColumn fx:id="colOriginalTotal" prefWidth="170.0" text="Total" />
</columns>
</TableView>
<Separator />
<Separator />
<HBox alignment="CENTER_LEFT" spacing="10.0">
<children>
<Label text="Refund Items" textFill="#2c3e50">
<HBox alignment="CENTER_LEFT" spacing="14.0">
<children>
<Label text="Refund Items" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
@@ -136,16 +136,16 @@
</Button>
</children>
</HBox>
<TableView fx:id="tvRefundItems" prefHeight="150.0" style="-fx-background-color: white; -fx-background-radius: 10;">
<columns>
<TableColumn fx:id="colRefundProduct" prefWidth="350.0" text="Product Name" />
<TableColumn fx:id="colRefundQuantity" prefWidth="120.0" text="Quantity" />
<TableColumn fx:id="colRefundUnitPrice" prefWidth="150.0" text="Unit Price" />
<TableColumn fx:id="colRefundTotal" prefWidth="150.0" text="Total" />
</columns>
</TableView>
</children>
</VBox>
<TableView fx:id="tvRefundItems" prefHeight="190.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #E8EBED; -fx-border-radius: 10;" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colRefundProduct" prefWidth="430.0" text="Product Name" />
<TableColumn fx:id="colRefundQuantity" prefWidth="140.0" text="Quantity" />
<TableColumn fx:id="colRefundUnitPrice" prefWidth="170.0" text="Unit Price" />
<TableColumn fx:id="colRefundTotal" prefWidth="170.0" text="Total" />
</columns>
</TableView>
</children>
</VBox>
<VBox spacing="12.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #FF6b6b; -fx-border-radius: 14;">
<padding>

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox prefHeight="520.0" prefWidth="760.0" spacing="18.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.dialogcontrollers.SaleDetailDialogController">
<padding>
<Insets bottom="18.0" left="18.0" right="18.0" top="18.0" />
</padding>
<children>
<HBox alignment="CENTER_LEFT" spacing="12.0" style="-fx-background-color: #4ECDC4; -fx-background-radius: 12;">
<padding>
<Insets bottom="14.0" left="16.0" right="16.0" top="14.0" />
</padding>
<children>
<Label text="Sale Details" textFill="WHITE">
<font>
<Font name="System Bold" size="24.0" />
</font>
</Label>
<Region HBox.hgrow="ALWAYS" />
<Button mnemonicParsing="false" onAction="#btnCloseClicked" style="-fx-background-color: white; -fx-text-fill: #2c3e50; -fx-background-radius: 8;" text="Close" />
</children>
</HBox>
<GridPane hgap="16.0" vgap="12.0" style="-fx-background-color: white; -fx-background-radius: 12; -fx-border-color: #e6e6e6; -fx-border-radius: 12; -fx-border-width: 1;">
<padding>
<Insets bottom="16.0" left="16.0" right="16.0" top="16.0" />
</padding>
<children>
<Label text="Sale ID" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label fx:id="lblSaleId" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<Label text="Date" GridPane.columnIndex="2" GridPane.rowIndex="0" />
<Label fx:id="lblSaleDate" GridPane.columnIndex="3" GridPane.rowIndex="0" />
<Label text="Employee" GridPane.columnIndex="0" GridPane.rowIndex="1" />
<Label fx:id="lblEmployee" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label text="Payment" GridPane.columnIndex="2" GridPane.rowIndex="1" />
<Label fx:id="lblPayment" GridPane.columnIndex="3" GridPane.rowIndex="1" />
<Label text="Total" GridPane.columnIndex="0" GridPane.rowIndex="2" />
<Label fx:id="lblTotal" GridPane.columnIndex="1" GridPane.rowIndex="2" />
</children>
</GridPane>
<TableView fx:id="tvItems" prefHeight="320.0" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colProduct" text="Product" prefWidth="330.0" />
<TableColumn fx:id="colQuantity" text="Qty" prefWidth="90.0" />
<TableColumn fx:id="colUnitPrice" text="Unit Price" prefWidth="130.0" />
<TableColumn fx:id="colLineTotal" text="Total" prefWidth="130.0" />
</columns>
</TableView>
</children>
</VBox>

View File

@@ -91,7 +91,7 @@
</padding>
</Label>
<Button fx:id="btnAnalytics" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAnalyticsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Analytics" textFill="#cbd5e1">
<Button fx:id="btnAnalytics" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAnalyticsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="📊 Analytics" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -99,7 +99,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</Button>
<Button fx:id="btnSalesHistory" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnSalesHistoryClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Sales History" textFill="#cbd5e1">
<Button fx:id="btnSalesHistory" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnSalesHistoryClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="💰 Sales History" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -107,7 +107,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</Button>
<Button fx:id="btnAppointments" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAppointmentsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Appointments" textFill="#cbd5e1">
<Button fx:id="btnAppointments" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAppointmentsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="📅 Appointments" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -115,7 +115,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</Button>
<Button fx:id="btnServices" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnServicesClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Services" textFill="#cbd5e1">
<Button fx:id="btnServices" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnServicesClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="✂️ Services" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -123,7 +123,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</Button>
<Button fx:id="btnChat" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnChatClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Chat" textFill="#cbd5e1">
<Button fx:id="btnChat" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnChatClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="💬 Chat" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -143,7 +143,7 @@
</padding>
</Label>
<Button fx:id="btnPets" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnPetsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Pets" textFill="#cbd5e1">
<Button fx:id="btnPets" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnPetsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="🐾 Pets" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -151,7 +151,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</Button>
<Button fx:id="btnAdoptions" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAdoptionsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Adoptions" textFill="#cbd5e1">
<Button fx:id="btnAdoptions" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnAdoptionsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="🏠 Adoptions" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -159,7 +159,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</Button>
<Button fx:id="btnProducts" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnProductsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Products" textFill="#cbd5e1">
<Button fx:id="btnProducts" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnProductsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="📦 Products" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -179,7 +179,7 @@
</padding>
</Label>
<Button fx:id="btnInventory" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnInventoryClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Inventory" textFill="#cbd5e1">
<Button fx:id="btnInventory" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnInventoryClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="📋 Inventory" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -187,7 +187,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</Button>
<Button fx:id="btnSuppliers" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnSuppliersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Suppliers" textFill="#cbd5e1">
<Button fx:id="btnSuppliers" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnSuppliersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="🚛 Suppliers" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -195,7 +195,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</Button>
<Button fx:id="btnProductSuppliers" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnProductSuppliersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Product-Suppliers" textFill="#cbd5e1">
<Button fx:id="btnProductSuppliers" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnProductSuppliersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="🔗 Product-Suppliers" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -203,7 +203,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</Button>
<Button fx:id="btnPurchaseOrders" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnPurchaseOrdersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Purchase Orders" textFill="#cbd5e1">
<Button fx:id="btnPurchaseOrders" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnPurchaseOrdersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="📝 Purchase Orders" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -211,7 +211,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</Button>
<Button fx:id="btnStaffAccounts" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnStaffAccountsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Staff Accounts" textFill="#cbd5e1">
<Button fx:id="btnStaffAccounts" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnStaffAccountsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="👥 Staff Accounts" textFill="#cbd5e1">
<font>
<Font name="System" size="12.0" />
</font>
@@ -220,7 +220,7 @@
</padding>
</Button>
<Button fx:id="btnLogout" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnLogoutClicked" style="-fx-background-color: rgba(255,255,255,0.08); -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Logout" textFill="#e2e8f0">
<Button fx:id="btnLogout" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnLogoutClicked" style="-fx-background-color: rgba(255,255,255,0.08); -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="🚪 Logout" textFill="#e2e8f0">
<font>
<Font name="System" size="12.0" />
</font>

View File

@@ -68,11 +68,12 @@
<TableView fx:id="tvAdoptions" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colAdoptionId" prefWidth="60.0" text="ID" />
<TableColumn fx:id="colPetId" prefWidth="66.2857666015625" text="Pet ID" />
<TableColumn fx:id="colCustomerName" prefWidth="200.57147216796875" text="Customer Name" />
<TableColumn fx:id="colAdoptionDate" prefWidth="190.85711669921875" text="Adoption Date" />
<TableColumn fx:id="colAdoptionFee" prefWidth="91.4285888671875" text="Fee" />
<TableColumn fx:id="colAdoptionStatus" prefWidth="142.28570556640625" text="Status" />
<TableColumn fx:id="colPetId" prefWidth="66.2857666015625" text="Pet ID" />
<TableColumn fx:id="colCustomerName" prefWidth="200.57147216796875" text="Customer Name" />
<TableColumn fx:id="colEmployeeName" prefWidth="160.0" text="Employee" />
<TableColumn fx:id="colAdoptionDate" prefWidth="190.85711669921875" text="Adoption Date" />
<TableColumn fx:id="colAdoptionFee" prefWidth="91.4285888671875" text="Fee" />
<TableColumn fx:id="colAdoptionStatus" prefWidth="120.0" text="Status" />
</columns>
</TableView>
</children>

View File

@@ -68,12 +68,13 @@
<TableView fx:id="tvAppointments" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colAppointmentId" prefWidth="53.14288330078125" text="ID" />
<TableColumn fx:id="colPetName" prefWidth="108.00003051757812" text="Pet Name" />
<TableColumn fx:id="colServiceName" prefWidth="132.0" text="Service" />
<TableColumn fx:id="colAppointmentDate" prefWidth="101.14288330078125" text="Date" />
<TableColumn fx:id="colAppointmentTime" prefWidth="89.7142333984375" text="Time" />
<TableColumn fx:id="colCustomerName" prefWidth="168.57147216796875" text="Customer" />
<TableColumn fx:id="colAppointmentStatus" prefWidth="98.28570556640625" text="Status" />
<TableColumn fx:id="colPetName" prefWidth="108.00003051757812" text="Pet Name" />
<TableColumn fx:id="colServiceName" prefWidth="132.0" text="Service" />
<TableColumn fx:id="colEmployeeName" prefWidth="132.0" text="Employee" />
<TableColumn fx:id="colAppointmentDate" prefWidth="101.14288330078125" text="Date" />
<TableColumn fx:id="colAppointmentTime" prefWidth="89.7142333984375" text="Time" />
<TableColumn fx:id="colCustomerName" prefWidth="140.0" text="Customer" />
<TableColumn fx:id="colAppointmentStatus" prefWidth="98.28570556640625" text="Status" />
</columns>
</TableView>
</children>

View File

@@ -2,6 +2,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
@@ -59,13 +60,15 @@
<Insets bottom="10.0" left="15.0" right="15.0" top="10.0" />
</padding>
<children>
<TextField fx:id="txtSearch" prefHeight="31.0" prefWidth="150.0" promptText="Search Pets..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
<font>
<Font size="15.0" />
</font>
</TextField>
</children>
</HBox>
<TextField fx:id="txtSearch" prefHeight="31.0" prefWidth="150.0" promptText="Search Pets..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
<font>
<Font size="15.0" />
</font>
</TextField>
<ComboBox fx:id="cbSpeciesFilter" prefWidth="150.0" promptText="Species" />
<ComboBox fx:id="cbStatusFilter" prefWidth="150.0" promptText="Status" />
</children>
</HBox>
<TableView fx:id="tvPets" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colPetId" prefWidth="55.0" text="ID" />
@@ -76,6 +79,8 @@
<TableColumn fx:id="colPetAge" prefWidth="60.0" text="Age" />
<TableColumn fx:id="colPetStatus" prefWidth="110.0" text="Status" />
<TableColumn fx:id="colPetPrice" prefWidth="80.0" text="Price" />
<TableColumn fx:id="colCustomerName" prefWidth="130.0" text="Owner" />
<TableColumn fx:id="colStoreName" prefWidth="130.0" text="Store" />
</columns>
</TableView>
</children>

View File

@@ -2,6 +2,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
@@ -58,13 +59,14 @@
<Insets bottom="10.0" left="15.0" right="15.0" top="10.0" />
</padding>
<children>
<TextField fx:id="txtSearch" prefHeight="31.0" prefWidth="150.0" promptText="Search products..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
<font>
<Font size="15.0" />
</font>
</TextField>
</children>
</HBox>
<TextField fx:id="txtSearch" prefHeight="31.0" prefWidth="150.0" promptText="Search products..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
<font>
<Font size="15.0" />
</font>
</TextField>
<ComboBox fx:id="cbCategoryFilter" prefWidth="180.0" promptText="Category" />
</children>
</HBox>
<TableView fx:id="tvProducts" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colProductId" prefWidth="55.0" text="ID" />

View File

@@ -37,7 +37,7 @@
<Insets top="10.0" />
</padding>
</Label>
<Region prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
<Region HBox.hgrow="ALWAYS" />
<Button fx:id="btnRefund" mnemonicParsing="false" onAction="#btnRefund" prefHeight="44.0" style="-fx-background-color: #FF6b6b; -fx-cursor: hand; -fx-background-radius: 8;" text="Process Refund" textFill="WHITE">
<font>
<Font name="System Bold" size="14.0" />
@@ -89,7 +89,7 @@
</Button>
</children>
</HBox>
<TableView fx:id="tvCart" prefHeight="170.0" style="-fx-background-color: white; -fx-background-radius: 10;" VBox.vgrow="NEVER">
<TableView fx:id="tvCart" prefHeight="170.0" style="-fx-background-color: white; -fx-background-radius: 10;" VBox.vgrow="NEVER">
<columns>
<TableColumn fx:id="colCartProduct" prefWidth="310.0" text="Product" />
<TableColumn fx:id="colCartQty" prefWidth="90.0" text="Qty" />
@@ -151,16 +151,16 @@
</TextField>
</children>
</HBox>
<TableView fx:id="tvSales" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12; -fx-padding: 6;" VBox.vgrow="ALWAYS">
<TableView fx:id="tvSales" prefHeight="362.0" style="-fx-background-color: white; -fx-background-radius: 12; -fx-padding: 6;" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colSaleId" minWidth="56.0" prefWidth="72.0" text="ID" />
<TableColumn fx:id="colSaleDate" minWidth="150.0" prefWidth="180.0" text="Date" />
<TableColumn fx:id="colEmployeeName" minWidth="150.0" prefWidth="180.0" text="Employee" />
<TableColumn fx:id="colServiceProduct" minWidth="190.0" prefWidth="240.0" text="Product" />
<TableColumn fx:id="colSaleQuantity" minWidth="70.0" prefWidth="80.0" text="Qty" />
<TableColumn fx:id="colSaleUnitPrice" minWidth="110.0" prefWidth="130.0" text="Unit Price" />
<TableColumn fx:id="colSaleTotal" minWidth="100.0" prefWidth="120.0" text="Total" />
<TableColumn fx:id="colSalePaymentType" minWidth="100.0" prefWidth="120.0" text="Payment" />
<TableColumn fx:id="colSaleId" minWidth="54.0" prefWidth="62.0" text="ID" />
<TableColumn fx:id="colSaleDate" minWidth="145.0" prefWidth="165.0" text="Date" />
<TableColumn fx:id="colEmployeeName" minWidth="130.0" prefWidth="155.0" text="Employee" />
<TableColumn fx:id="colServiceProduct" minWidth="180.0" prefWidth="240.0" text="Product" />
<TableColumn fx:id="colSaleQuantity" minWidth="62.0" prefWidth="72.0" text="Qty" />
<TableColumn fx:id="colSaleUnitPrice" minWidth="95.0" prefWidth="115.0" text="Unit Price" />
<TableColumn fx:id="colSaleTotal" minWidth="90.0" prefWidth="108.0" text="Total" />
<TableColumn fx:id="colSalePaymentType" minWidth="88.0" prefWidth="100.0" text="Payment" />
</columns>
</TableView>
</children>