From b3547b297105865c4540253dfb95c0aac489837e Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:08:14 -0600 Subject: [PATCH 1/7] fixed chat loading issue andriod --- .../adapters/MessageAdapter.java | 16 ++++++++++++++++ .../petstoremobile/fragments/ChatFragment.java | 8 +++++++- .../api/dto/chat/MessageResponse.java | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/MessageAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/MessageAdapter.java index 0c3a51a0..eb9d9c19 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/MessageAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/MessageAdapter.java @@ -11,6 +11,7 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.model.GlideUrl; import com.bumptech.glide.load.model.LazyHeaders; +import com.bumptech.glide.signature.ObjectKey; import com.example.petstoremobile.R; import com.example.petstoremobile.databinding.ItemMessageReceivedBinding; import com.example.petstoremobile.databinding.ItemMessageSentBinding; @@ -35,6 +36,13 @@ public class MessageAdapter extends RecyclerView.Adapter messages, Long currentUserId) { this.messages = messages; this.currentUserId = currentUserId; + setHasStableIds(true); + } + + @Override + public long getItemId(int position) { + Message m = messages.get(position); + return m.getId() != null ? m.getId() : position; } public void setCurrentUserId(Long id) { @@ -150,6 +158,7 @@ public class MessageAdapter extends RecyclerView.Adapter { + int prevSize = messageList.size(); messageList.clear(); messageList.addAll(list); - messageAdapter.notifyDataSetChanged(); + if (prevSize > 0 && list.size() == prevSize + 1) { + messageAdapter.notifyItemInserted(list.size() - 1); + } else { + messageAdapter.notifyDataSetChanged(); + } scrollToBottom(); }); diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java index 5f214731..3432a918 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java @@ -12,6 +12,8 @@ public class MessageResponse { private String content; private LocalDateTime timestamp; private Boolean isRead; + private String attachmentName; + private String attachmentUrl; public MessageResponse() { } @@ -87,4 +89,20 @@ public class MessageResponse { public void setIsRead(Boolean isRead) { this.isRead = isRead; } + + public String getAttachmentName() { + return attachmentName; + } + + public void setAttachmentName(String attachmentName) { + this.attachmentName = attachmentName; + } + + public String getAttachmentUrl() { + return attachmentUrl; + } + + public void setAttachmentUrl(String attachmentUrl) { + this.attachmentUrl = attachmentUrl; + } } From 6848ab3586a7e8c47231b92d39a61fa57bbe331f Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:15:15 -0600 Subject: [PATCH 2/7] implemented forget password for desktop --- .../petshopdesktop/api/endpoints/AuthApi.java | 8 ++++ .../controllers/LoginController.java | 40 +++++++++++++++++++ .../example/petshopdesktop/login-view.fxml | 10 +++++ 3 files changed, 58 insertions(+) diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AuthApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AuthApi.java index 0755ef9e..2a8c3417 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AuthApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AuthApi.java @@ -5,6 +5,8 @@ import org.example.petshopdesktop.api.dto.auth.AvatarUploadResponse; import org.example.petshopdesktop.api.dto.auth.UserInfoResponse; import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; public class AuthApi { private static final AuthApi INSTANCE = new AuthApi(); @@ -33,4 +35,10 @@ public class AuthApi { public void deleteAvatar() throws Exception { apiClient.delete("/api/v1/auth/me/avatar"); } + + public void forgotPassword(String usernameOrEmail) throws Exception { + Map body = new HashMap<>(); + body.put("usernameOrEmail", usernameOrEmail); + apiClient.post("/api/v1/auth/forgot-password", body, Object.class); + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/LoginController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/LoginController.java index 70c70e12..6c3427ea 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/LoginController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/LoginController.java @@ -1,12 +1,15 @@ package org.example.petshopdesktop.controllers; +import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; +import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; +import javafx.scene.control.TextInputDialog; import javafx.scene.control.TextField; import javafx.scene.layout.StackPane; import javafx.stage.Stage; @@ -14,6 +17,7 @@ import org.example.petshopdesktop.api.ApiClient; import org.example.petshopdesktop.api.dto.auth.LoginRequest; import org.example.petshopdesktop.api.dto.auth.LoginResponse; import org.example.petshopdesktop.api.dto.auth.UserInfoResponse; +import org.example.petshopdesktop.api.endpoints.AuthApi; import org.example.petshopdesktop.auth.Role; import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.ui.SvgWebViewFactory; @@ -105,6 +109,42 @@ public class LoginController { } } + @FXML + void lnkForgotPasswordClicked(ActionEvent event) { + TextInputDialog dialog = new TextInputDialog(); + dialog.setTitle("Forgot Password"); + dialog.setHeaderText("Reset your password"); + dialog.setContentText("Enter your username or email:"); + + dialog.showAndWait().ifPresent(input -> { + if (input.trim().isEmpty()) return; + new Thread(() -> { + try { + AuthApi.getInstance().forgotPassword(input.trim()); + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Reset Link Sent"); + alert.setHeaderText(null); + alert.setContentText("If this account exists, a password reset link has been sent to the associated email."); + alert.showAndWait(); + }); + } catch (Exception e) { + ActivityLogger.getInstance().logException( + "LoginController.lnkForgotPasswordClicked", + e, + "Forgot password request for: " + input.trim()); + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Error"); + alert.setHeaderText(null); + alert.setContentText("Could not send reset link. Please try again."); + alert.showAndWait(); + }); + } + }).start(); + }); + } + private void openMainLayout() { try { FXMLLoader loader = new FXMLLoader( diff --git a/desktop/src/main/resources/org/example/petshopdesktop/login-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/login-view.fxml index 1fe902ed..06b1eae8 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/login-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/login-view.fxml @@ -2,6 +2,7 @@ + @@ -83,6 +84,15 @@ + + + + + + From aca52efc447abf0516880b71bdac4cf5d0c309d2 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:33:10 -0600 Subject: [PATCH 3/7] maade it so sales display points earned --- .../detailfragments/SaleDetailFragment.java | 10 +++++++ .../main/res/layout/fragment_sale_detail.xml | 24 +++++++++++++++ .../controllers/SaleController.java | 29 ++++++++++++++++--- .../petshopdesktop/modelviews/sale-view.fxml | 6 ++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java index 8a4dff16..4963b272 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java @@ -111,6 +111,7 @@ public class SaleDetailFragment extends Fragment { binding.llLoyaltyPoints.setVisibility(View.GONE); binding.cbUseLoyaltyPoints.setChecked(false); } + updateTotal(); }); } @@ -420,6 +421,15 @@ public class SaleDetailFragment extends Fragment { } binding.tvSaleDetailTotal.setText("Total: $" + String.format(Locale.getDefault(), "%.2f", total)); + + CustomerDTO customer = viewModel.getSelectedCustomerData().getValue(); + if (customer != null && !viewModel.isViewOnly()) { + int pointsToEarn = total.max(BigDecimal.ZERO).intValue(); + binding.tvPointsToEarn.setText("+" + pointsToEarn + " pts"); + binding.llPointsToEarn.setVisibility(View.VISIBLE); + } else { + binding.llPointsToEarn.setVisibility(View.GONE); + } } private void saveSale() { diff --git a/android/app/src/main/res/layout/fragment_sale_detail.xml b/android/app/src/main/res/layout/fragment_sale_detail.xml index e92f83d4..7a5f66ec 100644 --- a/android/app/src/main/res/layout/fragment_sale_detail.xml +++ b/android/app/src/main/res/layout/fragment_sale_detail.xml @@ -444,6 +444,30 @@ android:textColor="@color/accent_coral" android:layout_gravity="end" android:layout_marginTop="12dp"/> + + + + + diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java index 39ed6b51..8eaaf7af 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java @@ -199,6 +199,12 @@ public class SaleController { @FXML private Label lblLoyaltyDiscount; + @FXML + private HBox hbPointsToEarn; + + @FXML + private Label lblPointsToEarn; + private final ObservableList cartItems = FXCollections.observableArrayList(); private final ObservableList saleItems = FXCollections.observableArrayList(); private FilteredList filteredSales; @@ -389,12 +395,15 @@ public class SaleController { task.setOnSucceeded(event -> { selectedCustomerData = task.getValue(); - if (selectedCustomerData != null && selectedCustomerData.getLoyaltyPoints() != null && selectedCustomerData.getLoyaltyPoints() >= 20) { - lblLoyaltyPoints.setText(selectedCustomerData.getLoyaltyPoints() + " pts available"); + if (selectedCustomerData != null) { + int pts = selectedCustomerData.getLoyaltyPoints() != null ? selectedCustomerData.getLoyaltyPoints() : 0; + lblLoyaltyPoints.setText(pts + " pts available"); lblLoyaltyPoints.setVisible(true); lblLoyaltyPoints.setManaged(true); - chkUseLoyaltyPoints.setVisible(true); - chkUseLoyaltyPoints.setManaged(true); + boolean canRedeem = pts >= 20; + chkUseLoyaltyPoints.setVisible(canRedeem); + chkUseLoyaltyPoints.setManaged(canRedeem); + if (!canRedeem) chkUseLoyaltyPoints.setSelected(false); } else { lblLoyaltyPoints.setVisible(false); lblLoyaltyPoints.setManaged(false); @@ -701,6 +710,8 @@ public class SaleController { hbCouponDiscount.setManaged(false); hbLoyaltyDiscount.setVisible(false); hbLoyaltyDiscount.setManaged(false); + hbPointsToEarn.setVisible(false); + hbPointsToEarn.setManaged(false); cbCustomer.setValue(null); selectedCustomerData = null; lblLoyaltyPoints.setVisible(false); @@ -875,6 +886,16 @@ public class SaleController { } lblCartTotal.setText(currency.format(Math.max(0, total.doubleValue()))); + + if (selectedCustomerData != null) { + int pointsToEarn = (int) Math.max(0, total.doubleValue()); + lblPointsToEarn.setText("+" + pointsToEarn + " pts"); + hbPointsToEarn.setVisible(true); + hbPointsToEarn.setManaged(true); + } else { + hbPointsToEarn.setVisible(false); + hbPointsToEarn.setManaged(false); + } } private BigDecimal calculateCouponDiscount(BigDecimal subtotal) { diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml index 20f9ae16..666c2898 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml @@ -173,6 +173,12 @@ + + + + + + From 9c47f5ac76cad0447adf7bbbd1217252b10aa5d2 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:43:24 -0600 Subject: [PATCH 4/7] added staff and customer images to desktop --- .../CustomerAccountsController.java | 35 +++++++++++++++++++ .../controllers/StaffAccountsController.java | 33 +++++++++++++++++ .../modelviews/customer-accounts-view.fxml | 1 + .../modelviews/staff-accounts-view.fxml | 1 + 4 files changed, 70 insertions(+) diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java index a4e073f3..eddf0df9 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java @@ -7,13 +7,17 @@ import javafx.collections.transformation.FilteredList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; import javafx.stage.Modality; import javafx.stage.Stage; import org.example.petshopdesktop.api.dto.user.UserResponse; import org.example.petshopdesktop.api.endpoints.CustomerApi; import org.example.petshopdesktop.util.ActivityLogger; +import org.example.petshopdesktop.util.DesktopImageSupport; import org.example.petshopdesktop.util.TableViewSupport; import java.util.Comparator; @@ -25,6 +29,9 @@ public class CustomerAccountsController { @FXML private TableView tvCustomers; + @FXML + private TableColumn colCustomerAvatar; + @FXML private TableColumn colCustomerUsername; @@ -69,6 +76,13 @@ public class CustomerAccountsController { @FXML public void initialize() { + colCustomerAvatar.setCellValueFactory(data -> { + Long id = data.getValue().getId(); + if (id == null) return new javafx.beans.property.SimpleStringProperty(""); + return new javafx.beans.property.SimpleStringProperty("/api/v1/users/" + id + "/avatar/file"); + }); + configureAvatarColumn(colCustomerAvatar); + colCustomerUsername.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getUsername())); colCustomerName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName())); colCustomerEmail.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getEmail())); @@ -94,6 +108,27 @@ public class CustomerAccountsController { refresh(); } + private void configureAvatarColumn(TableColumn column) { + column.setCellFactory(col -> new TableCell<>() { + private final ImageView imageView = new ImageView(); + private final StackPane container = new StackPane(imageView); + { + container.setAlignment(Pos.CENTER); + } + + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null || item.isBlank()) { + setGraphic(null); + return; + } + DesktopImageSupport.loadImageInto(imageView, item, 48, 48); + setGraphic(container); + } + }); + } + @FXML void btnRefreshClicked(ActionEvent event) { txtSearchCustomer.clear(); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java index a91d6080..36c7378b 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java @@ -7,8 +7,11 @@ import javafx.collections.transformation.FilteredList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.stage.Modality; import javafx.stage.Stage; @@ -16,6 +19,7 @@ import org.example.petshopdesktop.api.dto.employee.EmployeeResponse; import org.example.petshopdesktop.api.endpoints.EmployeeApi; import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.util.ActivityLogger; +import org.example.petshopdesktop.util.DesktopImageSupport; import org.example.petshopdesktop.util.TableViewSupport; import java.util.Comparator; @@ -26,6 +30,7 @@ public class StaffAccountsController { @FXML private VBox staffSection; @FXML private TableView tvStaff; + @FXML private TableColumn colStaffAvatar; @FXML private TableColumn colUsername; @FXML private TableColumn colName; @FXML private TableColumn colEmail; @@ -47,6 +52,13 @@ public class StaffAccountsController { @FXML public void initialize() { + colStaffAvatar.setCellValueFactory(data -> { + Long id = data.getValue().getId(); + if (id == null) return new javafx.beans.property.SimpleStringProperty(""); + return new javafx.beans.property.SimpleStringProperty("/api/v1/users/" + id + "/avatar/file"); + }); + configureAvatarColumn(colStaffAvatar); + colUsername.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getUsername())); colName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName())); colEmail.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getEmail())); @@ -81,6 +93,27 @@ public class StaffAccountsController { refresh(); } + private void configureAvatarColumn(TableColumn column) { + column.setCellFactory(col -> new TableCell<>() { + private final ImageView imageView = new ImageView(); + private final StackPane container = new StackPane(imageView); + { + container.setAlignment(Pos.CENTER); + } + + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null || item.isBlank()) { + setGraphic(null); + return; + } + DesktopImageSupport.loadImageInto(imageView, item, 48, 48); + setGraphic(container); + } + }); + } + @FXML void btnRefreshClicked(ActionEvent event) { txtSearch.clear(); diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/customer-accounts-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/customer-accounts-view.fxml index d7156dab..dd28302c 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/customer-accounts-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/customer-accounts-view.fxml @@ -69,6 +69,7 @@ + diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/staff-accounts-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/staff-accounts-view.fxml index c92851fc..d3b4c8d4 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/staff-accounts-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/staff-accounts-view.fxml @@ -78,6 +78,7 @@ + From 7340a5616e6cefbfe0d6b2c101f7290b8d11578b Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:53:42 -0600 Subject: [PATCH 5/7] Changed android phone validation --- .../com/example/petstoremobile/utils/InputValidator.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/InputValidator.java b/android/app/src/main/java/com/example/petstoremobile/utils/InputValidator.java index 0d45ad12..9ad2d5f4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/InputValidator.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/InputValidator.java @@ -100,13 +100,12 @@ public class InputValidator { return true; } - // Checks if the phone number is valid in (XXX) XXX-XXXX format + // Checks if the phone number is valid: XXX-XXX-XXXX, (XXX) XXX-XXXX, or XXXXXXXXXX public static boolean isValidPhone(EditText field) { String phone = field.getText().toString().trim(); - // Matches (XXX) XXX-XXXX format - String pattern = "^\\(\\d{3}\\) \\d{3}-\\d{4}$"; + String pattern = "^(\\(\\d{3}\\) \\d{3}-\\d{4}|\\d{3}-\\d{3}-\\d{4}|\\d{10})$"; if (phone.isEmpty() || !phone.matches(pattern)) { - field.setError("Enter a valid phone number: (XXX) XXX-XXXX"); + field.setError("Enter a valid phone number"); field.requestFocus(); return false; } From ec0d2d1ec7c8a156eb182c078eabbcc2db3ca5b3 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:10:03 -0600 Subject: [PATCH 6/7] added personal and store analytics --- .../listfragments/AnalyticsFragment.java | 55 +++++++++++++--- .../viewmodels/AnalyticsViewModel.java | 27 +++++++- .../main/res/layout/fragment_analytics.xml | 34 ++++++++++ .../controllers/AnalyticsController.java | 63 +++++++++++++++++-- .../modelviews/analytics-view.fxml | 11 ++++ 5 files changed, 173 insertions(+), 17 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java index b8656b60..1b45569f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java @@ -7,11 +7,13 @@ import android.widget.*; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; +import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.databinding.FragmentAnalyticsBinding; import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.UIUtils; import com.example.petstoremobile.viewmodels.AnalyticsViewModel; import dagger.hilt.android.AndroidEntryPoint; +import javax.inject.Inject; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; @@ -19,6 +21,9 @@ import java.util.*; @AndroidEntryPoint public class AnalyticsFragment extends Fragment { + @Inject + TokenManager tokenManager; + private FragmentAnalyticsBinding binding; private AnalyticsViewModel viewModel; private boolean filtersExpanded = false; @@ -33,6 +38,7 @@ public class AnalyticsFragment extends Fragment { viewModel = new ViewModelProvider(this).get(AnalyticsViewModel.class); setupFilterPanel(); + setupViewModeToggle(); observeViewModel(); viewModel.loadAnalytics(); @@ -42,6 +48,30 @@ public class AnalyticsFragment extends Fragment { return binding.getRoot(); } + private static final int COLOR_SELECTED = 0xFF4ECDC4; + private static final int COLOR_UNSELECTED = 0xFFCBD5E1; + + private void setupViewModeToggle() { + updateViewModeButtonStyles(viewModel.getViewMode()); + + binding.btnMyAnalytics.setOnClickListener(v -> { + viewModel.setViewMode("mine"); + updateViewModeButtonStyles("mine"); + }); + + binding.btnStoreAnalytics.setOnClickListener(v -> { + viewModel.setViewMode("store"); + updateViewModeButtonStyles("store"); + }); + } + + private void updateViewModeButtonStyles(String mode) { + binding.btnMyAnalytics.setBackgroundTintList( + android.content.res.ColorStateList.valueOf(mode.equals("mine") ? COLOR_SELECTED : COLOR_UNSELECTED)); + binding.btnStoreAnalytics.setBackgroundTintList( + android.content.res.ColorStateList.valueOf(mode.equals("store") ? COLOR_SELECTED : COLOR_UNSELECTED)); + } + // Filter Panel private void setupFilterPanel() { @@ -224,17 +254,22 @@ public class AnalyticsFragment extends Fragment { } // Employee Performance - binding.llEmployeePerformance.removeAllViews(); - if (data.employeePerformance != null && !data.employeePerformance.isEmpty()) { - BigDecimal maxEmp = data.employeePerformance.get(0).getValue(); - if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE; - for (Map.Entry e : data.employeePerformance) { - addBarRow(binding.llEmployeePerformance, e.getKey(), - "$" + e.getValue().setScale(2, RoundingMode.HALF_UP), - e.getValue().floatValue() / maxEmp.floatValue(), "#1a759f"); + boolean showEmployeeSection = viewModel.getViewMode().equals("store"); + View empParent = (View) binding.llEmployeePerformance.getParent(); + if (empParent != null) empParent.setVisibility(showEmployeeSection ? View.VISIBLE : View.GONE); + if (showEmployeeSection) { + binding.llEmployeePerformance.removeAllViews(); + if (data.employeePerformance != null && !data.employeePerformance.isEmpty()) { + BigDecimal maxEmp = data.employeePerformance.get(0).getValue(); + if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE; + for (Map.Entry e : data.employeePerformance) { + addBarRow(binding.llEmployeePerformance, e.getKey(), + "$" + e.getValue().setScale(2, RoundingMode.HALF_UP), + e.getValue().floatValue() / maxEmp.floatValue(), "#1a759f"); + } + } else { + addEmptyRow(binding.llEmployeePerformance, "No data"); } - } else { - addEmptyRow(binding.llEmployeePerformance, "No data"); } // Daily Revenue diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java index 721dad5f..7b69bc47 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.dtos.SaleDTO; import com.example.petstoremobile.repositories.SaleRepository; import com.example.petstoremobile.utils.Resource; @@ -21,6 +22,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -29,6 +31,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel; @HiltViewModel public class AnalyticsViewModel extends ViewModel { private final SaleRepository saleRepository; + private final TokenManager tokenManager; private final MutableLiveData analyticsData = new MutableLiveData<>(); private final MutableLiveData isLoading = new MutableLiveData<>(false); @@ -37,10 +40,12 @@ public class AnalyticsViewModel extends ViewModel { private List cachedSales = new ArrayList<>(); private FilterState currentFilter = new FilterState(); + private String viewMode = "store"; @Inject - public AnalyticsViewModel(SaleRepository saleRepository) { + public AnalyticsViewModel(SaleRepository saleRepository, TokenManager tokenManager) { this.saleRepository = saleRepository; + this.tokenManager = tokenManager; } public LiveData getAnalyticsData() { return analyticsData; } @@ -76,8 +81,26 @@ public class AnalyticsViewModel extends ViewModel { applyCurrentFilter(); } + public void setViewMode(String mode) { + viewMode = mode; + applyCurrentFilter(); + } + + public String getViewMode() { + return viewMode; + } + private void applyCurrentFilter() { - List filtered = filterSales(cachedSales, currentFilter); + List salesForMode; + if (viewMode.equals("mine")) { + String currentUser = tokenManager.getUsername(); + salesForMode = cachedSales.stream() + .filter(s -> currentUser != null && currentUser.equalsIgnoreCase(s.getEmployeeName() != null ? s.getEmployeeName() : "")) + .collect(Collectors.toList()); + } else { + salesForMode = cachedSales; + } + List filtered = filterSales(salesForMode, currentFilter); computeAnalytics(filtered, currentFilter); } diff --git a/android/app/src/main/res/layout/fragment_analytics.xml b/android/app/src/main/res/layout/fragment_analytics.xml index 19a7a51b..2e375f58 100644 --- a/android/app/src/main/res/layout/fragment_analytics.xml +++ b/android/app/src/main/res/layout/fragment_analytics.xml @@ -45,6 +45,40 @@ + + +