add order history to profile

This commit is contained in:
2026-04-16 00:38:10 -06:00
parent 26791de867
commit 4d6166a882
3 changed files with 129 additions and 1 deletions

View File

@@ -3,6 +3,7 @@ package com.petshop.backend.controller;
import com.petshop.backend.dto.sale.SaleRequest;
import com.petshop.backend.dto.sale.SaleResponse;
import com.petshop.backend.service.SaleService;
import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -21,6 +22,13 @@ public class SaleController {
this.saleService = saleService;
}
@GetMapping("/my")
@PreAuthorize("hasAnyRole('CUSTOMER', 'ADMIN')")
public ResponseEntity<Page<SaleResponse>> getMyOrders(Pageable pageable) {
Long userId = AuthenticationHelper.getAuthenticatedUserId();
return ResponseEntity.ok(saleService.getAllSales(null, null, null, false, userId, pageable));
}
@GetMapping
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity<Page<SaleResponse>> getAllSales(

View File

@@ -2075,6 +2075,68 @@ body {
color: #333;
}
.profile-orders-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.profile-order-card {
border: 1px solid #f0f0f0;
border-radius: 10px;
padding: 0.85rem 1rem;
background: #fafafa;
}
.profile-order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.25rem;
}
.profile-order-date {
font-size: 0.85rem;
color: #555;
font-weight: 600;
}
.profile-order-total {
font-size: 0.95rem;
font-weight: 700;
color: #222;
}
.profile-order-meta {
display: flex;
gap: 1rem;
font-size: 0.78rem;
color: #999;
margin-bottom: 0.5rem;
}
.profile-order-items {
margin: 0.25rem 0 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 0.2rem;
border-top: 1px solid #ececec;
padding-top: 0.5rem;
}
.profile-order-items li {
display: flex;
justify-content: space-between;
font-size: 0.82rem;
color: #444;
}
.profile-order-item-price {
color: #888;
}
/* Store Selector */
.nav-store-select {

View File

@@ -25,6 +25,8 @@ export default function ProfilePage() {
const [pets, setPets] = useState([]);
const [loadingPets, setLoadingPets] = useState(false);
const [orders, setOrders] = useState([]);
const [loadingOrders, setLoadingOrders] = useState(false);
const [showForm, setShowForm] = useState(false);
const [editingPet, setEditingPet] = useState(null);
const [petName, setPetName] = useState("");
@@ -120,11 +122,27 @@ export default function ProfilePage() {
};
}, [clearPetImageObjectUrls]);
const loadOrders = useCallback(async () => {
if (!token) return;
setLoadingOrders(true);
try {
const res = await fetch(`${API_BASE}/api/v1/sales/my?size=20&sort=saleDate,desc`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) return;
const data = await res.json();
setOrders(data.content ?? []);
} catch { } finally {
setLoadingOrders(false);
}
}, [token]);
useEffect(() => {
if (user?.role === "CUSTOMER" || user?.role === "ADMIN") {
loadPets();
loadOrders();
}
}, [user, loadPets]);
}, [user, loadPets, loadOrders]);
useEffect(() => {
let objectUrl = null;
@@ -642,6 +660,46 @@ export default function ProfilePage() {
)}
</div>
)}
{(user.role === "CUSTOMER" || user.role === "ADMIN") && (
<div className="profile-pets-section">
<div className="profile-pets-header">
<h2 className="profile-pets-title">Order History</h2>
</div>
{loadingOrders ? (
<p className="appt-loading">Loading orders...</p>
) : orders.length === 0 ? (
<p className="profile-pets-empty">No orders yet.</p>
) : (
<div className="profile-orders-list">
{orders.map((order) => (
<div key={order.saleId} className="profile-order-card">
<div className="profile-order-header">
<span className="profile-order-date">
{new Date(order.saleDate).toLocaleDateString([], { year: "numeric", month: "short", day: "numeric" })}
</span>
<span className="profile-order-total">${Number(order.totalAmount).toFixed(2)}</span>
</div>
<div className="profile-order-meta">
<span>{order.storeName}</span>
{order.paymentMethod && <span>{order.paymentMethod}</span>}
</div>
{order.items?.length > 0 && (
<ul className="profile-order-items">
{order.items.map((item) => (
<li key={item.saleItemId}>
<span>{item.productName} × {item.quantity}</span>
<span className="profile-order-item-price">${(Number(item.unitPrice) * item.quantity).toFixed(2)}</span>
</li>
))}
</ul>
)}
</div>
))}
</div>
)}
</div>
)}
</main>
);
}