comment desktop controllers

This commit is contained in:
2026-04-20 16:22:39 -06:00
parent 3a155b6c03
commit 049e142845
33 changed files with 332 additions and 0 deletions

View File

@@ -1,3 +1,9 @@
/*
* Controls the activity log screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the pet adoption management screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the analytics and reporting screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the appointment scheduling screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
@@ -132,6 +138,10 @@ public class AppointmentController {
loadAppointments();
}
/**
* Fetches appointments from the API (filtered by store and optionally by employee)
* and updates both the table and the calendar event dots.
*/
private void loadAppointments(){
new Thread(() -> {
try{
@@ -165,6 +175,11 @@ public class AppointmentController {
}).start();
}
/**
* Sends a server-side search query and replaces the local list with results.
* Also refreshes calendar dots to match the filtered set.
* @param text the search query text
*/
private void applyFilter(String text) {
String query = text == null || text.trim().isEmpty() ? null : text.trim();
new Thread(() -> {
@@ -334,6 +349,10 @@ public class AppointmentController {
return (selected != null && selected.getId() != null) ? selected.getId() : null;
}
/**
* Applies the client-side filters (calendar date selection and status dropdown)
* on top of whatever the server already returned.
*/
private void applyFilterPredicate() {
String selectedStatus = cbStatusFilter.getValue();
filtered.setPredicate(apt -> {
@@ -372,6 +391,12 @@ public class AppointmentController {
);
}
/**
* Normalizes the status string from the API to title case for display.
* Handles inconsistencies like "cancelled" vs "canceled".
* @param status raw status from the server
* @return normalized display string
*/
private String normalizeAppointmentStatus(String status) {
if (status == null) {
return "Booked";

View File

@@ -1,3 +1,9 @@
/*
* Controls the in-app messaging screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
@@ -105,6 +111,8 @@ public class ChatController {
lvActiveConversations.setItems(activeConversations);
lvClosedConversations.setItems(closedConversations);
// The two lists share a single selection — picking one clears the other.
// selectionChanging flag prevents the clear from triggering a feedback loop.
lvActiveConversations.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
if (newVal != null && !selectionChanging) {
selectionChanging = true;
@@ -304,6 +312,11 @@ public class ChatController {
btnAttachment.setStyle("-fx-background-color: #e2e8f0; -fx-background-radius: 12; -fx-text-fill: #475569; -fx-cursor: hand;");
}
/**
* Configures a conversation list with custom cells showing title, preview, and status.
* Highlights conversations that need pickup or reply in red.
* @param lv the ListView to configure
*/
private void setupConversationListView(ListView<ConversationResponse> lv) {
lv.setFixedCellSize(CHAT_CELL_HEIGHT);
lv.setCellFactory(list -> new ListCell<>() {
@@ -453,6 +466,12 @@ public class ChatController {
scrollMessagesToBottom();
}
/**
* Handles an incoming message from the WebSocket.
* Only adds it to the view if it belongs to the currently selected conversation
* and hasn't already been rendered (dedup by message ID).
* @param message the incoming message
*/
private void appendMessageIfSelected(MessageResponse message) {
try {
upsertConversationForMessage(message);
@@ -474,6 +493,11 @@ public class ChatController {
}
}
/**
* Inserts or updates a conversation in the list and re-sorts by last activity.
* Called when the WebSocket pushes a conversation update (e.g. status change).
* @param conversation the updated conversation from the server
*/
private void upsertConversation(ConversationResponse conversation) {
Optional<ConversationResponse> existing = conversations.stream()
.filter(item -> item.getId().equals(conversation.getId()))
@@ -507,6 +531,10 @@ public class ChatController {
});
}
/**
* Re-selects the previously selected conversation after the list is rebuilt.
* Needed because refreshSections() replaces all items, which clears the selection.
*/
private void restoreSelection() {
if (selectedConversation == null) {
return;
@@ -528,6 +556,12 @@ public class ChatController {
});
}
/**
* Builds a single chat bubble with avatar, author label, text, optional
* image/attachment preview, and timestamp. Own messages go right, others left.
* @param message the message to render
* @return the bubble wrapped in an HBox
*/
private HBox createMessageBubble(MessageResponse message) {
boolean mine = message.getSenderId() != null && message.getSenderId().equals(UserSession.getInstance().getUserId());
@@ -609,6 +643,13 @@ public class ChatController {
return container;
}
/**
* Figures out the display label for a message sender.
* Shows "You" for own messages, "AI Bot" for bot messages, and
* "Staff" or "Customer" for everyone else.
* @param message the message to resolve
* @return the author display name
*/
private String resolveAuthorLabel(MessageResponse message) {
if ("BOT".equalsIgnoreCase(message.getSenderRole())) {
return message.getSenderDisplayName() != null && !message.getSenderDisplayName().isBlank()
@@ -630,6 +671,12 @@ public class ChatController {
return customerLabel != null ? customerLabel : "Customer #" + conversation.getCustomerId();
}
/**
* Builds the small status line under each conversation in the sidebar.
* Shows assignment state (Automated, Assigned, Takeover requested, etc.) and time.
* @param conversation the conversation to summarize
* @return a short status string like "Assigned · Apr 10, 14:30"
*/
private String buildConversationMeta(ConversationResponse conversation) {
String updated = conversation.getUpdatedAt() == null ? "" : TIME_FORMATTER.format(conversation.getUpdatedAt());
if ("CLOSED".equals(conversation.getStatus())) {

View File

@@ -1,3 +1,9 @@
/*
* Controls the coupon management screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the customer accounts screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the inventory management screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the login screen and handles authentication.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the main layout with sidebar navigation.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;
@@ -339,6 +345,10 @@ public class MainLayoutController {
}
}
/**
* Fetches the current user info from the server on a background thread
* and updates the sidebar avatar and display name.
*/
private void refreshProfileHeader() {
new Thread(() -> {
try {
@@ -376,6 +386,12 @@ public class MainLayoutController {
}
}
/**
* Renders the avatar in the sidebar. Shows the uploaded image if available,
* otherwise shows a colored circle with the user's initials.
* @param displayName used to generate initials as a fallback
* @param avatarImage the user's profile image, or null for the initials fallback
*/
private void renderAvatar(String displayName, Image avatarImage) {
Circle border = new Circle(29);
border.setFill(Color.web("#dbe4ee"));
@@ -427,6 +443,11 @@ public class MainLayoutController {
alert.showAndWait();
}
/**
* Shows or hides sidebar buttons based on the user's role.
* Admin gets inventory, suppliers, staff accounts, etc.
* Also kicks off chat WebSocket subscription in the background.
*/
private void applyRBAC() {
UserSession session = UserSession.getInstance();
@@ -498,6 +519,10 @@ public class MainLayoutController {
}).start();
}
/**
* Swaps the main content area to a different view.
* @param fxmlFile the FXML filename inside the modelviews folder
*/
private void loadView(String fxmlFile) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/modelviews/" + fxmlFile));
@@ -551,6 +576,11 @@ public class MainLayoutController {
}
}
/**
* Maps EXIF orientation tag values to rotation angles.
* @param orientation the EXIF orientation value (1-8)
* @return degrees to rotate the image
*/
private double exifOrientationToAngle(int orientation) {
return switch (orientation) {
case 3 -> 180;
@@ -560,6 +590,13 @@ public class MainLayoutController {
};
}
/**
* Manually reads the EXIF orientation tag from raw JPEG bytes.
* Walks the JPEG markers to find APP1 (Exif), then parses the TIFF IFD
* entries looking for tag 0x0112 (orientation).
* @param data raw JPEG image bytes
* @return the orientation value (1-8), defaults to 1 if not found
*/
private int readExifOrientation(byte[] data) {
try {
if (data.length < 2 || (data[0] & 0xFF) != 0xFF || (data[1] & 0xFF) != 0xD8) return 1;

View File

@@ -1,3 +1,9 @@
/*
* Controls the pet listings screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the product catalog screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.animation.KeyFrame;

View File

@@ -1,3 +1,9 @@
/*
* Controls the product-supplier linking screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the purchase order management screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the sales and transactions screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.collections.FXCollections;
@@ -427,6 +433,10 @@ public class SaleController {
new Thread(task).start();
}
/**
* Shows or hides the create-sale panel based on role.
* Admins get read-only view; staff can create sales and process refunds.
*/
private void applyRoleMode() {
boolean isAdmin = UserSession.getInstance().isAdmin();
vbCreateSale.setVisible(!isAdmin);
@@ -440,6 +450,12 @@ public class SaleController {
refreshSales(false);
}
/**
* Fetches all sales from the API and flattens them into per-item line rows.
* Applies any active store filter. Discounts are distributed proportionally
* across line items using a ratio of total to subtotal.
* @param showErrorDialog whether to show error dialogs on failure
*/
private void refreshSales(boolean showErrorDialog) {
btnRefresh.setDisable(true);
Task<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
@@ -456,6 +472,7 @@ public class SaleController {
: "";
if (sale.getItems() != null && !sale.getItems().isEmpty()) {
// Spread the discount evenly across line items via a ratio
double saleSubtotal = sale.getSubtotalAmount() != null ? Math.abs(sale.getSubtotalAmount().doubleValue()) : 0;
double saleActualTotal = sale.getTotalAmount() != null ? Math.abs(sale.getTotalAmount().doubleValue()) : 0;
double discountRatio = saleSubtotal > 0 ? saleActualTotal / saleSubtotal : 1.0;
@@ -816,6 +833,12 @@ public class SaleController {
new Thread(task).start();
}
/**
* Converts a SaleResponse from the API into a SaleDetail for the detail dialog.
* Distributes discounts across line items the same way refreshSales does.
* @param sale the API response
* @return the mapped detail object
*/
private SaleDetail mapToSaleDetail(SaleResponse sale) {
ObservableList<SaleDetail.SaleDetailItem> items = FXCollections.observableArrayList();
double saleSubtotal = sale.getSubtotalAmount() != null ? Math.abs(sale.getSubtotalAmount().doubleValue()) : 0;
@@ -858,6 +881,10 @@ public class SaleController {
);
}
/**
* Recalculates the cart summary: subtotal, coupon discount, loyalty discount, and final total.
* Also updates the "points to earn" preview for the selected customer.
*/
private void updateCartTotal() {
BigDecimal subtotal = BigDecimal.valueOf(cartItems.stream().mapToDouble(SaleCartItem::getTotal).sum());
BigDecimal couponDiscount = calculateCouponDiscount(subtotal);
@@ -900,6 +927,12 @@ public class SaleController {
}
}
/**
* Calculates how much the coupon takes off. Percentage coupons use the
* subtotal; fixed-amount coupons are capped at the subtotal.
* @param subtotal the cart subtotal before discounts
* @return the discount amount
*/
private BigDecimal calculateCouponDiscount(BigDecimal subtotal) {
if (appliedCoupon == null) {
return BigDecimal.ZERO;
@@ -911,6 +944,11 @@ public class SaleController {
}
}
/**
* Calculates loyalty point discount. Each point is worth $0.05.
* @param subtotalAfterCoupon the amount after coupon discount
* @return the loyalty discount amount
*/
private BigDecimal calculateLoyaltyDiscount(BigDecimal subtotalAfterCoupon) {
if (!chkUseLoyaltyPoints.isSelected() || selectedCustomerData == null) {
return BigDecimal.ZERO;
@@ -919,6 +957,13 @@ public class SaleController {
return BigDecimal.valueOf(pointsToUse).multiply(BigDecimal.valueOf(0.05));
}
/**
* Figures out how many loyalty points to redeem. Uses up to the customer's
* full balance, but never more than would cover the remaining total.
* The multiplier of 20 means 20 points = $1.00 of discount.
* @param subtotalAfterCoupon amount left after coupon
* @return number of points to use
*/
private int calculatePointsToUse(BigDecimal subtotalAfterCoupon) {
if (selectedCustomerData == null || selectedCustomerData.getLoyaltyPoints() == null) {
return 0;
@@ -998,6 +1043,10 @@ public class SaleController {
chkUseLoyaltyPoints.setDisable(disabled);
}
/**
* Applies the combined text search, payment type, refund status, and customer filters.
* @param filter the text search input
*/
private void applySalesFilter(String filter) {
String f = filter == null ? "" : filter.trim().toLowerCase();
String selectedPayment = cbFilterPayment.getValue();

View File

@@ -1,3 +1,9 @@
/*
* Controls the services management screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the staff accounts screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the supplier management screen.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for creating or editing an adoption.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for creating or editing an appointment.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for creating or editing a coupon.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.collections.FXCollections;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for editing customer details.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for adjusting inventory levels.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for adding or editing a pet.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for adding or editing a product.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.collections.FXCollections;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for linking a product to a supplier.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog that shows purchase order details.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.fxml.FXML;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for processing a refund.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.collections.FXCollections;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog that shows sale details.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.fxml.FXML;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for creating or editing a service.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for editing staff member details.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for registering a new staff member.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;

View File

@@ -1,3 +1,9 @@
/*
* Controls the dialog for adding or editing a supplier.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.event.EventHandler;