Feedback remarks added
This commit is contained in:
3447
backend/my_startup.log
Normal file
3447
backend/my_startup.log
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,10 +4,12 @@ import com.rit.canteen.sales.model.Feedback;
|
|||||||
import com.rit.canteen.sales.model.ItemRating;
|
import com.rit.canteen.sales.model.ItemRating;
|
||||||
import com.rit.canteen.sales.model.Order;
|
import com.rit.canteen.sales.model.Order;
|
||||||
import com.rit.canteen.sales.repository.FeedbackRepository;
|
import com.rit.canteen.sales.repository.FeedbackRepository;
|
||||||
|
import com.rit.canteen.sales.repository.ItemRatingRepository;
|
||||||
import com.rit.canteen.sales.repository.OrderRepository;
|
import com.rit.canteen.sales.repository.OrderRepository;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -24,6 +26,9 @@ public class FeedbackController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private OrderRepository orderRepository;
|
private OrderRepository orderRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ItemRatingRepository itemRatingRepository;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public Page<Feedback> getAllFeedback(
|
public Page<Feedback> getAllFeedback(
|
||||||
@RequestParam(defaultValue = "0") int page,
|
@RequestParam(defaultValue = "0") int page,
|
||||||
@@ -32,29 +37,66 @@ public class FeedbackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/item-details")
|
@GetMapping("/item-details")
|
||||||
|
@Transactional(readOnly = true)
|
||||||
public Page<Map<String, Object>> getItemDetails(
|
public Page<Map<String, Object>> getItemDetails(
|
||||||
@RequestParam String productName,
|
@RequestParam String productName,
|
||||||
@RequestParam(defaultValue = "0") int page,
|
@RequestParam(defaultValue = "0") int page,
|
||||||
@RequestParam(defaultValue = "10") int size) {
|
@RequestParam(defaultValue = "10") int size) {
|
||||||
Page<ItemRating> ratings = feedbackRepository.findByProductName(productName, PageRequest.of(page, size));
|
String trimmedName = productName != null ? productName.trim() : "";
|
||||||
|
|
||||||
|
// Sort by feedback.createdAt DESC
|
||||||
|
Sort sort = Sort.by(Sort.Direction.DESC, "feedback.createdAt");
|
||||||
|
Page<ItemRating> ratings = itemRatingRepository.findByProductNameIgnoreCase(trimmedName, PageRequest.of(page, size, sort));
|
||||||
|
|
||||||
return ratings.map(r -> {
|
return ratings.map(r -> {
|
||||||
Map<String, Object> map = new HashMap<>();
|
Map<String, Object> map = new HashMap<>();
|
||||||
map.put("rating", r.getRating());
|
map.put("rating", r.getRating());
|
||||||
|
|
||||||
// Priority: Item-specific comment -> parent Feedback comment -> empty
|
// Priority: Item-specific comment -> parent Feedback comment -> empty
|
||||||
String comment = r.getComment();
|
String comment = r.getComment();
|
||||||
|
Feedback f = r.getFeedback();
|
||||||
if (comment == null || comment.isBlank()) {
|
if (comment == null || comment.isBlank()) {
|
||||||
if (r.getFeedback() != null) {
|
if (f != null) {
|
||||||
comment = r.getFeedback().getComment();
|
comment = f.getComment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
map.put("comment", comment != null ? comment : "");
|
map.put("comment", comment != null ? comment : "");
|
||||||
map.put("date", r.getFeedback() != null ? r.getFeedback().getCreatedAt() : java.time.LocalDateTime.now());
|
map.put("date", f != null ? f.getCreatedAt() : java.time.LocalDateTime.now());
|
||||||
|
map.put("userName", f != null ? f.getUserName() : "Anonymous");
|
||||||
|
map.put("orderNumber", (f != null && f.getOrder() != null) ? f.getOrder().getOrderNumber() : "N/A");
|
||||||
return map;
|
return map;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/item-stats")
|
||||||
|
public Map<String, Object> getItemStats(@RequestParam String productName) {
|
||||||
|
Map<String, Object> stats = new HashMap<>();
|
||||||
|
List<Object[]> distribution = feedbackRepository.getItemRatingDistribution(productName);
|
||||||
|
List<Map<String, Object>> distList = new ArrayList<>();
|
||||||
|
|
||||||
|
long totalCount = 0;
|
||||||
|
double sum = 0;
|
||||||
|
|
||||||
|
for (Object[] row : distribution) {
|
||||||
|
Map<String, Object> item = new HashMap<>();
|
||||||
|
int rating = ((Number) row[0]).intValue();
|
||||||
|
long count = ((Number) row[1]).longValue();
|
||||||
|
item.put("rating", rating);
|
||||||
|
item.put("count", count);
|
||||||
|
distList.add(item);
|
||||||
|
|
||||||
|
totalCount += count;
|
||||||
|
sum += (rating * count);
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.put("distribution", distList);
|
||||||
|
stats.put("totalReviews", totalCount);
|
||||||
|
stats.put("averageRating", totalCount > 0 ? sum / totalCount : 0.0);
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/stats")
|
@GetMapping("/stats")
|
||||||
public Map<String, Object> getFeedbackStats() {
|
public Map<String, Object> getFeedbackStats() {
|
||||||
Map<String, Object> stats = new HashMap<>();
|
Map<String, Object> stats = new HashMap<>();
|
||||||
@@ -66,8 +108,8 @@ public class FeedbackController {
|
|||||||
List<Map<String, Object>> distList = new ArrayList<>();
|
List<Map<String, Object>> distList = new ArrayList<>();
|
||||||
for (Object[] row : distribution) {
|
for (Object[] row : distribution) {
|
||||||
Map<String, Object> item = new HashMap<>();
|
Map<String, Object> item = new HashMap<>();
|
||||||
item.put("rating", row[0]);
|
item.put("rating", ((Number) row[0]).intValue());
|
||||||
item.put("count", row[1]);
|
item.put("count", ((Number) row[1]).longValue());
|
||||||
distList.add(item);
|
distList.add(item);
|
||||||
}
|
}
|
||||||
stats.put("distribution", distList);
|
stats.put("distribution", distList);
|
||||||
@@ -77,8 +119,8 @@ public class FeedbackController {
|
|||||||
for (Object[] row : topRated) {
|
for (Object[] row : topRated) {
|
||||||
Map<String, Object> item = new HashMap<>();
|
Map<String, Object> item = new HashMap<>();
|
||||||
item.put("name", row[0]);
|
item.put("name", row[0]);
|
||||||
item.put("average", row[1]);
|
item.put("average", row[1] != null ? ((Number) row[1]).doubleValue() : 0.0);
|
||||||
item.put("count", row[2]);
|
item.put("count", row[2] != null ? ((Number) row[2]).longValue() : 0);
|
||||||
topList.add(item);
|
topList.add(item);
|
||||||
}
|
}
|
||||||
stats.put("ratedItems", topList);
|
stats.put("ratedItems", topList);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.rit.canteen.sales.repository;
|
package com.rit.canteen.sales.repository;
|
||||||
|
|
||||||
import com.rit.canteen.sales.model.Feedback;
|
import com.rit.canteen.sales.model.Feedback;
|
||||||
|
import com.rit.canteen.sales.model.ItemRating;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
@@ -25,6 +26,6 @@ public interface FeedbackRepository extends JpaRepository<Feedback, Long> {
|
|||||||
@Query("SELECT ir.productName, AVG(ir.rating), COUNT(ir) FROM ItemRating ir GROUP BY ir.productName ORDER BY AVG(ir.rating) DESC")
|
@Query("SELECT ir.productName, AVG(ir.rating), COUNT(ir) FROM ItemRating ir GROUP BY ir.productName ORDER BY AVG(ir.rating) DESC")
|
||||||
List<Object[]> getTopRatedItems();
|
List<Object[]> getTopRatedItems();
|
||||||
|
|
||||||
@Query("SELECT ir FROM ItemRating ir WHERE TRIM(LOWER(ir.productName)) = TRIM(LOWER(:productName)) ORDER BY ir.feedback.createdAt DESC")
|
@Query("SELECT ir.rating, COUNT(ir) FROM ItemRating ir WHERE TRIM(LOWER(ir.productName)) = TRIM(LOWER(:productName)) GROUP BY ir.rating")
|
||||||
Page<com.rit.canteen.sales.model.ItemRating> findByProductName(@org.springframework.data.repository.query.Param("productName") String productName, Pageable pageable);
|
List<Object[]> getItemRatingDistribution(@org.springframework.data.repository.query.Param("productName") String productName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.rit.canteen.sales.repository;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.model.ItemRating;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ItemRatingRepository extends JpaRepository<ItemRating, Long> {
|
||||||
|
|
||||||
|
@Query(value = "SELECT ir FROM ItemRating ir LEFT JOIN FETCH ir.feedback WHERE TRIM(LOWER(ir.productName)) = TRIM(LOWER(:productName))",
|
||||||
|
countQuery = "SELECT COUNT(ir) FROM ItemRating ir WHERE TRIM(LOWER(ir.productName)) = TRIM(LOWER(:productName))")
|
||||||
|
Page<ItemRating> findByProductNameIgnoreCase(String productName, Pageable pageable);
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -17,6 +18,15 @@ public class OrderArchiverService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private OrderRepository orderRepository;
|
private OrderRepository orderRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures all old orders are archived immediately upon application startup.
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
System.out.println("Application startup: Triggering proactive order archival...");
|
||||||
|
archivePreviousDayOrders();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically archives orders from previous days at midnight (12:00 AM).
|
* Automatically archives orders from previous days at midnight (12:00 AM).
|
||||||
* Cron: 0 0 0 * * * (Second Minute Hour Day Month DayOfWeek)
|
* Cron: 0 0 0 * * * (Second Minute Hour Day Month DayOfWeek)
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
PieChart as PieChartIcon,
|
PieChart as PieChartIcon,
|
||||||
ThumbsUp,
|
ThumbsUp,
|
||||||
X,
|
X,
|
||||||
Clock
|
Clock,
|
||||||
|
BarChart3
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
PieChart,
|
PieChart,
|
||||||
@@ -36,6 +37,17 @@ interface ItemDetail {
|
|||||||
rating: number;
|
rating: number;
|
||||||
comment: string;
|
comment: string;
|
||||||
date: string;
|
date: string;
|
||||||
|
userName?: string;
|
||||||
|
orderNumber?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ItemStats {
|
||||||
|
averageRating: number;
|
||||||
|
totalReviews: number;
|
||||||
|
distribution: Array<{
|
||||||
|
rating: number;
|
||||||
|
count: number;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FeedbackStats {
|
interface FeedbackStats {
|
||||||
@@ -60,6 +72,7 @@ const Feedback: React.FC = () => {
|
|||||||
// Modal states
|
// Modal states
|
||||||
const [selectedItem, setSelectedItem] = useState<RatedItem | null>(null);
|
const [selectedItem, setSelectedItem] = useState<RatedItem | null>(null);
|
||||||
const [itemDetails, setItemDetails] = useState<ItemDetail[]>([]);
|
const [itemDetails, setItemDetails] = useState<ItemDetail[]>([]);
|
||||||
|
const [itemStats, setItemStats] = useState<ItemStats | null>(null);
|
||||||
const [detailsLoading, setItemDetailsLoading] = useState(false);
|
const [detailsLoading, setItemDetailsLoading] = useState(false);
|
||||||
const [detailsPage, setDetailsPage] = useState(0);
|
const [detailsPage, setDetailsPage] = useState(0);
|
||||||
const [detailsTotal, setDetailsTotal] = useState(0);
|
const [detailsTotal, setDetailsTotal] = useState(0);
|
||||||
@@ -90,15 +103,22 @@ const Feedback: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchItemDetails = async (itemName: string, pageNum: number) => {
|
const fetchItemData = async (itemName: string, pageNum: number) => {
|
||||||
setItemDetailsLoading(true);
|
setItemDetailsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://${window.location.hostname}:8080/api/feedback/item-details?productName=${encodeURIComponent(itemName)}&page=${pageNum}&size=${detailsPageSize}`);
|
const [detailsRes, statsRes] = await Promise.all([
|
||||||
const data = await response.json();
|
fetch(`http://${window.location.hostname}:8080/api/feedback/item-details?productName=${encodeURIComponent(itemName)}&page=${pageNum}&size=${detailsPageSize}`),
|
||||||
setItemDetails(data?.content || []);
|
fetch(`http://${window.location.hostname}:8080/api/feedback/item-stats?productName=${encodeURIComponent(itemName)}`)
|
||||||
setDetailsTotal(data?.totalElements || 0);
|
]);
|
||||||
|
|
||||||
|
const detailsData = await detailsRes.json();
|
||||||
|
const statsData = await statsRes.json();
|
||||||
|
|
||||||
|
setItemDetails(detailsData?.content || []);
|
||||||
|
setDetailsTotal(detailsData?.totalElements || 0);
|
||||||
|
setItemStats(statsData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching item details:', error);
|
console.error('Error fetching item feedback data:', error);
|
||||||
setItemDetails([]);
|
setItemDetails([]);
|
||||||
} finally {
|
} finally {
|
||||||
setItemDetailsLoading(false);
|
setItemDetailsLoading(false);
|
||||||
@@ -115,7 +135,7 @@ const Feedback: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedItem) {
|
if (selectedItem) {
|
||||||
fetchItemDetails(selectedItem.name, detailsPage);
|
fetchItemData(selectedItem.name, detailsPage);
|
||||||
}
|
}
|
||||||
}, [selectedItem, detailsPage]);
|
}, [selectedItem, detailsPage]);
|
||||||
|
|
||||||
@@ -359,7 +379,46 @@ const Feedback: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-8 overflow-y-auto flex-1 bg-slate-50/50">
|
<div className="p-8 overflow-y-auto flex-1 bg-slate-50/50">
|
||||||
<div className="space-y-4">
|
<div className="space-y-6">
|
||||||
|
{/* Item Specific Breakdown */}
|
||||||
|
{itemStats && (
|
||||||
|
<div className="bg-white p-6 rounded-[2rem] border border-slate-100 shadow-sm">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<div className="p-2 bg-amber-50 text-amber-500 rounded-xl">
|
||||||
|
<BarChart3 size={18} />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-sm font-black text-slate-800 uppercase tracking-widest">Rating Breakdown</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{[5, 4, 3, 2, 1].map(rating => {
|
||||||
|
const dist = itemStats.distribution.find(d => d.rating === rating);
|
||||||
|
const percentage = itemStats.totalReviews > 0 ? ((dist?.count || 0) / itemStats.totalReviews) * 100 : 0;
|
||||||
|
return (
|
||||||
|
<div key={rating} className="flex items-center gap-4">
|
||||||
|
<span className="text-xs font-bold text-slate-400 w-12">{rating} Stars</span>
|
||||||
|
<div className="flex-1 h-2 bg-slate-100 rounded-full overflow-hidden">
|
||||||
|
<motion.div
|
||||||
|
initial={{ width: 0 }}
|
||||||
|
animate={{ width: `${percentage}%` }}
|
||||||
|
className="h-full bg-amber-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-black text-slate-600 w-8">{dist?.count || 0}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 mt-4">
|
||||||
|
<div className="p-2 bg-indigo-50 text-indigo-500 rounded-xl">
|
||||||
|
<MessageSquare size={18} />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-sm font-black text-slate-800 uppercase tracking-widest">Customer Remarks</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
{detailsLoading && itemDetails.length === 0 ? (
|
{detailsLoading && itemDetails.length === 0 ? (
|
||||||
<div className="py-20 text-center">
|
<div className="py-20 text-center">
|
||||||
<RefreshCw size={32} className="animate-spin mx-auto text-indigo-200 mb-4" />
|
<RefreshCw size={32} className="animate-spin mx-auto text-indigo-200 mb-4" />
|
||||||
@@ -367,22 +426,23 @@ const Feedback: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : itemDetails.map((detail, idx) => (
|
) : itemDetails.map((detail, idx) => (
|
||||||
<div key={idx} className="bg-white p-6 rounded-3xl border border-slate-100 shadow-sm">
|
<div key={idx} className="bg-white p-6 rounded-3xl border border-slate-100 shadow-sm">
|
||||||
<div className="flex justify-between items-start mb-3">
|
<div className="flex justify-between items-start mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 bg-slate-50 rounded-xl text-slate-400">
|
<div className="p-2 bg-slate-50 rounded-xl text-slate-400">
|
||||||
<Clock size={16} />
|
<Clock size={16} />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs font-bold text-slate-400 uppercase">{format(new Date(detail.date), 'dd MMM yyyy, hh:mm a')}</span>
|
<span className="text-[11px] font-bold text-slate-400 uppercase tracking-wider">{format(new Date(detail.date), 'dd MMM yyyy, hh:mm a')}</span>
|
||||||
</div>
|
</div>
|
||||||
{renderStars(detail.rating, 16)}
|
{renderStars(detail.rating, 18)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-600 leading-relaxed font-medium italic">
|
|
||||||
"{detail.comment || 'No written review provided'}"
|
<div className="bg-slate-50/80 rounded-2xl p-5 border border-slate-100 relative">
|
||||||
|
<div className="absolute -top-3 left-4 bg-white px-2 text-[8px] font-black text-indigo-400 uppercase tracking-widest border border-slate-100 rounded">REMARK</div>
|
||||||
|
<p className="text-slate-700 leading-relaxed font-semibold italic text-sm">
|
||||||
|
"{detail.comment || 'No specific remark provided.'}"
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 pt-4 border-t border-slate-50 flex items-center gap-2">
|
|
||||||
<div className="w-6 h-6 bg-indigo-50 rounded-full flex items-center justify-center text-[10px] font-black text-indigo-500">A</div>
|
|
||||||
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Anonymous Customer</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ interface Order {
|
|||||||
status: string;
|
status: string;
|
||||||
paymentMethod: string;
|
paymentMethod: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
archived: boolean;
|
isArchived: boolean;
|
||||||
items: Array<{
|
items: Array<{
|
||||||
productName: string;
|
productName: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
@@ -42,6 +42,13 @@ const MyOrdersScreen: React.FC = () => {
|
|||||||
const [unavailableItems, setUnavailableItems] = useState<string[]>([]);
|
const [unavailableItems, setUnavailableItems] = useState<string[]>([]);
|
||||||
const [pendingItems, setPendingItems] = useState<FoodItem[]>([]);
|
const [pendingItems, setPendingItems] = useState<FoodItem[]>([]);
|
||||||
|
|
||||||
|
const isOrderExpired = (order: Order) => {
|
||||||
|
if (order.isArchived) return true;
|
||||||
|
const orderDate = new Date(order.createdAt).toDateString();
|
||||||
|
const today = new Date().toDateString();
|
||||||
|
return orderDate !== today;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user?.id) {
|
if (user?.id) {
|
||||||
fetchOrders();
|
fetchOrders();
|
||||||
@@ -137,7 +144,7 @@ const MyOrdersScreen: React.FC = () => {
|
|||||||
<div className="order-status-badge">{latestOrder.status}</div>
|
<div className="order-status-badge">{latestOrder.status}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`latest-qr-wrapper ${latestOrder.status.toUpperCase() === 'COMPLETED' ? 'qr-completed' : ''} ${latestOrder.archived ? 'qr-expired' : ''}`}>
|
<div className={`latest-qr-wrapper ${latestOrder.status.toUpperCase() === 'COMPLETED' ? 'qr-completed' : ''} ${isOrderExpired(latestOrder) ? 'qr-expired' : ''}`}>
|
||||||
<div className="qr-container">
|
<div className="qr-container">
|
||||||
<QRCodeCanvas value={latestOrder.orderNumber} size={120} className="mini-qr" />
|
<QRCodeCanvas value={latestOrder.orderNumber} size={120} className="mini-qr" />
|
||||||
{latestOrder.status.toUpperCase() === 'COMPLETED' && (
|
{latestOrder.status.toUpperCase() === 'COMPLETED' && (
|
||||||
@@ -145,14 +152,14 @@ const MyOrdersScreen: React.FC = () => {
|
|||||||
<span className="overlay-text mini">COMPLETED</span>
|
<span className="overlay-text mini">COMPLETED</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{latestOrder.archived && (
|
{isOrderExpired(latestOrder) && (
|
||||||
<div className="qr-overlay mini">
|
<div className="qr-overlay mini">
|
||||||
<span className="overlay-text mini expired">EXPIRED</span>
|
<span className="overlay-text mini expired">EXPIRED</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="qr-hint-text">
|
<div className="qr-hint-text">
|
||||||
{latestOrder.status.toUpperCase() === 'COMPLETED' ? 'Order Fulfilled' : 'Tap to enlarge QR'}
|
{isOrderExpired(latestOrder) ? 'QR Expired' : (latestOrder.status.toUpperCase() === 'COMPLETED' ? 'Order Fulfilled' : 'Tap to enlarge QR')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -237,7 +244,7 @@ const MyOrdersScreen: React.FC = () => {
|
|||||||
<p className="modal-order-id">Order #{selectedOrder.displayOrderId}</p>
|
<p className="modal-order-id">Order #{selectedOrder.displayOrderId}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`modal-qr-section ${selectedOrder.status.toUpperCase() === 'COMPLETED' ? 'qr-completed' : ''} ${selectedOrder.archived ? 'qr-expired' : ''}`}>
|
<div className={`modal-qr-section ${selectedOrder.status.toUpperCase() === 'COMPLETED' ? 'qr-completed' : ''} ${isOrderExpired(selectedOrder) ? 'qr-expired' : ''}`}>
|
||||||
<div className="qr-container">
|
<div className="qr-container">
|
||||||
<QRCodeCanvas value={selectedOrder.orderNumber} size={200} includeMargin={true} />
|
<QRCodeCanvas value={selectedOrder.orderNumber} size={200} includeMargin={true} />
|
||||||
{selectedOrder.status.toUpperCase() === 'COMPLETED' && (
|
{selectedOrder.status.toUpperCase() === 'COMPLETED' && (
|
||||||
@@ -245,15 +252,15 @@ const MyOrdersScreen: React.FC = () => {
|
|||||||
<span className="overlay-text">COMPLETED</span>
|
<span className="overlay-text">COMPLETED</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{selectedOrder.archived && (
|
{isOrderExpired(selectedOrder) && (
|
||||||
<div className="qr-overlay">
|
<div className="qr-overlay">
|
||||||
<span className="overlay-text expired">EXPIRED</span>
|
<span className="overlay-text expired">EXPIRED</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="modal-qr-hint">
|
<p className="modal-qr-hint">
|
||||||
{selectedOrder.archived
|
{isOrderExpired(selectedOrder)
|
||||||
? 'This order has expired'
|
? 'QR Expired'
|
||||||
: selectedOrder.status.toUpperCase() === 'COMPLETED'
|
: selectedOrder.status.toUpperCase() === 'COMPLETED'
|
||||||
? 'This order has been fulfilled'
|
? 'This order has been fulfilled'
|
||||||
: 'Show this QR code at the counter'}
|
: 'Show this QR code at the counter'}
|
||||||
|
|||||||
Reference in New Issue
Block a user