Counter dashboard global search added

This commit is contained in:
Sidharth Prabhu
2026-06-24 08:08:12 +05:30
parent a914fd44cc
commit c3bb004dc6
10 changed files with 252 additions and 114 deletions

View File

@@ -284,13 +284,28 @@ public class OrderController {
existingOrder.setTotalAmount(newAmount); existingOrder.setTotalAmount(newAmount);
existingOrder.setPaymentMethod(updatedOrder.getPaymentMethod()); existingOrder.setPaymentMethod(updatedOrder.getPaymentMethod());
existingOrder.getItems().clear(); if (updatedOrder.getStatus() != null) {
existingOrder.setStatus(updatedOrder.getStatus().toUpperCase());
}
// Safely copy items into new instances (without IDs) to trigger correct orphan removal
List<OrderItem> newItems = new ArrayList<>();
if (updatedOrder.getItems() != null) { if (updatedOrder.getItems() != null) {
for (OrderItem newItem : updatedOrder.getItems()) { for (OrderItem item : updatedOrder.getItems()) {
OrderItem newItem = new OrderItem();
newItem.setProductId(item.getProductId());
newItem.setProductName(item.getProductName());
newItem.setPrice(item.getPrice());
newItem.setQuantity(item.getQuantity());
newItem.setStallId(item.getStallId());
newItem.setStallName(item.getStallName());
newItem.setOrder(existingOrder); newItem.setOrder(existingOrder);
existingOrder.getItems().add(newItem); newItems.add(newItem);
} }
} }
existingOrder.getItems().clear();
existingOrder.getItems().addAll(newItems);
Order saved = orderRepository.save(existingOrder); Order saved = orderRepository.save(existingOrder);
return ResponseEntity.ok(saved); return ResponseEntity.ok(saved);
}).orElse(ResponseEntity.notFound().build()); }).orElse(ResponseEntity.notFound().build());

View File

@@ -28,7 +28,12 @@ public class PurchaseController {
private SystemNotificationService notificationService; private SystemNotificationService notificationService;
@GetMapping("/orders") @GetMapping("/orders")
public List<PurchaseOrder> getAllOrders() { public List<PurchaseOrder> getAllOrders(
@RequestParam(required = false) @org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME) java.time.LocalDateTime from,
@RequestParam(required = false) @org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME) java.time.LocalDateTime to) {
if (from != null && to != null) {
return purchaseService.getAllOrdersInRange(from, to);
}
return purchaseService.getAllOrders(); return purchaseService.getAllOrders();
} }
@@ -83,7 +88,12 @@ public class PurchaseController {
} }
@GetMapping("/summary") @GetMapping("/summary")
public Map<String, Object> getSummary() { public Map<String, Object> getSummary(
@RequestParam(required = false) @org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME) java.time.LocalDateTime from,
@RequestParam(required = false) @org.springframework.format.annotation.DateTimeFormat(iso = org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME) java.time.LocalDateTime to) {
if (from != null && to != null) {
return purchaseService.getPurchaseSummaryInRange(from, to);
}
return purchaseService.getPurchaseSummary(); return purchaseService.getPurchaseSummary();
} }

View File

@@ -23,6 +23,12 @@ public interface PurchaseOrderRepository extends JpaRepository<PurchaseOrder, Lo
@org.springframework.data.jpa.repository.Query("SELECT SUM(p.amount) FROM PurchaseOrder p WHERE p.date >= :start AND p.date <= :end") @org.springframework.data.jpa.repository.Query("SELECT SUM(p.amount) FROM PurchaseOrder p WHERE p.date >= :start AND p.date <= :end")
java.math.BigDecimal getTotalPurchaseAmountInRange(@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start, @org.springframework.data.repository.query.Param("end") java.time.LocalDateTime end); java.math.BigDecimal getTotalPurchaseAmountInRange(@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start, @org.springframework.data.repository.query.Param("end") java.time.LocalDateTime end);
@org.springframework.data.jpa.repository.Query("SELECT SUM(COALESCE(p.amount, 0) - COALESCE(p.paidTotal, 0)) FROM PurchaseOrder p WHERE p.date >= :start AND p.date <= :end")
java.math.BigDecimal getTotalBalanceAmountInRange(@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start, @org.springframework.data.repository.query.Param("end") java.time.LocalDateTime end);
@org.springframework.data.jpa.repository.Query("SELECT COUNT(p) FROM PurchaseOrder p WHERE p.status != 'PAID' AND p.date >= :start AND p.date <= :end")
long countUnpaidBillsInRange(@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start, @org.springframework.data.repository.query.Param("end") java.time.LocalDateTime end);
@org.springframework.data.jpa.repository.Query("SELECT p.vendor.name, SUM(p.amount), COUNT(p) FROM PurchaseOrder p WHERE p.date >= :start AND p.date <= :end GROUP BY p.vendor.name") @org.springframework.data.jpa.repository.Query("SELECT p.vendor.name, SUM(p.amount), COUNT(p) FROM PurchaseOrder p WHERE p.date >= :start AND p.date <= :end GROUP BY p.vendor.name")
List<Object[]> getVendorSummary(@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start, @org.springframework.data.repository.query.Param("end") java.time.LocalDateTime end); List<Object[]> getVendorSummary(@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start, @org.springframework.data.repository.query.Param("end") java.time.LocalDateTime end);
@@ -32,6 +38,9 @@ public interface PurchaseOrderRepository extends JpaRepository<PurchaseOrder, Lo
@org.springframework.data.jpa.repository.Query("SELECT p.date, SUM(p.amount) FROM PurchaseOrder p GROUP BY p.date ORDER BY p.date ASC") @org.springframework.data.jpa.repository.Query("SELECT p.date, SUM(p.amount) FROM PurchaseOrder p GROUP BY p.date ORDER BY p.date ASC")
List<Object[]> getPurchaseTrend(); List<Object[]> getPurchaseTrend();
@org.springframework.data.jpa.repository.Query("SELECT p FROM PurchaseOrder p WHERE p.date >= :start AND p.date <= :end ORDER BY p.date DESC")
List<PurchaseOrder> findByDateInRange(@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start, @org.springframework.data.repository.query.Param("end") java.time.LocalDateTime end);
long countByStatus(String status); long countByStatus(String status);
@org.springframework.data.jpa.repository.Query("SELECT SUM(i.quantity) FROM PurchaseOrder p JOIN p.items i WHERE p.status = 'OPEN'") @org.springframework.data.jpa.repository.Query("SELECT SUM(i.quantity) FROM PurchaseOrder p JOIN p.items i WHERE p.status = 'OPEN'")

View File

@@ -66,18 +66,27 @@ public class DevicePairingService {
public Map<String, Object> registerOtp(String otp, String deviceId) { public Map<String, Object> registerOtp(String otp, String deviceId) {
PairingRequest existing = pendingPairings.get(otp); PairingRequest existing = pendingPairings.get(otp);
// If this OTP was already linked by admin, return the apiKey if (existing != null) {
if (existing != null && existing.completed && existing.deviceId.equals(deviceId)) { // If this OTP was already linked by admin, return the apiKey
// Clean up after delivering the key if (existing.completed) {
pendingPairings.remove(otp); // Clean up after delivering the key
return Map.of( pendingPairings.remove(otp);
"status", "PAIRED", return Map.of(
"apiKey", existing.apiKey, "status", "PAIRED",
"terminalId", existing.terminalId "apiKey", existing.apiKey,
); "terminalId", existing.terminalId
);
}
// If the existing request has a placeholder device ID but this call has a real one, update it
if (existing.deviceId.equals("ESP32-Device") && !deviceId.equals("ESP32-Device")) {
pendingPairings.put(otp, new PairingRequest(otp, deviceId));
}
return Map.of("status", "WAITING");
} }
// Register or refresh the OTP // Register new OTP
pendingPairings.put(otp, new PairingRequest(otp, deviceId)); pendingPairings.put(otp, new PairingRequest(otp, deviceId));
return Map.of("status", "WAITING"); return Map.of("status", "WAITING");

View File

@@ -40,6 +40,10 @@ public class PurchaseService {
return purchaseOrderRepository.findAll(); return purchaseOrderRepository.findAll();
} }
public List<PurchaseOrder> getAllOrdersInRange(java.time.LocalDateTime start, java.time.LocalDateTime end) {
return purchaseOrderRepository.findByDateInRange(start, end);
}
public List<Vendor> getAllVendors() { public List<Vendor> getAllVendors() {
return vendorRepository.findAll(); return vendorRepository.findAll();
} }
@@ -132,6 +136,30 @@ public class PurchaseService {
return summary; return summary;
} }
public Map<String, Object> getPurchaseSummaryInRange(java.time.LocalDateTime start, java.time.LocalDateTime end) {
Map<String, Object> summary = new HashMap<>();
BigDecimal total = purchaseOrderRepository.getTotalPurchaseAmountInRange(start, end);
BigDecimal balance = purchaseOrderRepository.getTotalBalanceAmountInRange(start, end);
long unpaidCount = purchaseOrderRepository.countUnpaidBillsInRange(start, end);
summary.put("totalAmount", total != null ? total : BigDecimal.ZERO);
summary.put("balanceAmount", balance != null ? balance : BigDecimal.ZERO);
summary.put("paidAmount", (total != null ? total : BigDecimal.ZERO).subtract(balance != null ? balance : BigDecimal.ZERO));
summary.put("unpaidCount", unpaidCount);
List<Object[]> trendData = purchaseOrderRepository.getPurchaseTrendInRange(start, end);
List<Map<String, Object>> trend = new ArrayList<>();
for (Object[] row : trendData) {
Map<String, Object> point = new HashMap<>();
point.put("date", row[0].toString());
point.put("amount", row[1]);
trend.add(point);
}
summary.put("trend", trend);
return summary;
}
public Map<String, Object> getIntentSummary() { public Map<String, Object> getIntentSummary() {
Map<String, Object> summary = new HashMap<>(); Map<String, Object> summary = new HashMap<>();
summary.put("openCount", purchaseOrderRepository.countByStatus("OPEN")); summary.put("openCount", purchaseOrderRepository.countByStatus("OPEN"));

View File

@@ -80,9 +80,7 @@ function App() {
<Route path="purchases/intent/receives-dashboard" element={<IntentDashboard title="RECEIVABLE DASHBOARD" />} /> <Route path="purchases/intent/receives-dashboard" element={<IntentDashboard title="RECEIVABLE DASHBOARD" />} />
<Route path="purchases/intent/orders" element={<IntentList title="ORDERS" />} /> <Route path="purchases/intent/orders" element={<IntentList title="ORDERS" />} />
<Route path="purchases/intent/receives" element={<IntentList title="RECEIVES" />} /> <Route path="purchases/intent/receives" element={<IntentList title="RECEIVES" />} />
<Route path="purchases/intent/receives-summary" element={<PlaceholderPage title="Receives Summary" />} />
<Route path="purchases/intent/request" element={<PlaceholderPage title="Intent Request" />} />
<Route path="purchases/intent/stores" element={<PlaceholderPage title="Intent Stores" />} />
{/* Inventory */} {/* Inventory */}
<Route path="inventory/new-arrivals" element={<NewArrivals />} /> <Route path="inventory/new-arrivals" element={<NewArrivals />} />

View File

@@ -80,10 +80,7 @@ const menuItems: MenuItem[] = [
{ title: 'Orders Dashboard', path: '/purchases/intent/orders-dashboard' }, { title: 'Orders Dashboard', path: '/purchases/intent/orders-dashboard' },
{ title: 'Receives Dashboard', path: '/purchases/intent/receives-dashboard' }, { title: 'Receives Dashboard', path: '/purchases/intent/receives-dashboard' },
{ title: 'Orders', path: '/purchases/intent/orders' }, { title: 'Orders', path: '/purchases/intent/orders' },
{ title: 'Receives', path: '/purchases/intent/receives' }, { title: 'Receives', path: '/purchases/intent/receives' }
{ title: 'Receives Summary', path: '/purchases/intent/receives-summary' },
{ title: 'Request', path: '/purchases/intent/request' },
{ title: 'Stores', path: '/purchases/intent/stores' }
] ]
} }
] ]

View File

@@ -19,18 +19,86 @@ import { motion } from 'framer-motion';
const PurchaseSummary = () => { const PurchaseSummary = () => {
const [activeRange, setActiveRange] = useState('Today'); const [activeRange, setActiveRange] = useState('Today');
const [customDates, setCustomDates] = useState({ from: '', to: '' });
const [data, setData] = useState<any>(null); const [data, setData] = useState<any>(null);
const [bills, setBills] = useState<any[]>([]); const [bills, setBills] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const toLocalISOString = (date: Date) => {
const tzo = -date.getTimezoneOffset(),
pad = (num: number) => {
const norm = Math.floor(Math.abs(num));
return (norm < 10 ? '0' : '') + norm;
};
return date.getFullYear() +
'-' + pad(date.getMonth() + 1) +
'-' + pad(date.getDate()) +
'T' + pad(date.getHours()) +
':' + pad(date.getMinutes()) +
':' + pad(date.getSeconds()) +
'.' + pad(date.getMilliseconds());
};
const getRangeDates = (range: string) => {
const now = new Date();
const start = new Date();
const end = new Date();
// Set end to end of today
end.setHours(23, 59, 59, 999);
switch (range) {
case 'Today':
start.setHours(0, 0, 0, 0);
break;
case 'Yesterday':
start.setDate(now.getDate() - 1);
start.setHours(0, 0, 0, 0);
end.setDate(now.getDate() - 1);
end.setHours(23, 59, 59, 999);
break;
case 'Week':
start.setDate(now.getDate() - 7);
start.setHours(0, 0, 0, 0);
break;
case '30 Days':
start.setDate(now.getDate() - 30);
start.setHours(0, 0, 0, 0);
break;
case 'Custom':
if (customDates.from && customDates.to) {
const from = new Date(customDates.from);
from.setHours(0, 0, 0, 0);
const to = new Date(customDates.to);
to.setHours(23, 59, 59, 999);
return { from: toLocalISOString(from), to: toLocalISOString(to) };
}
return null;
default:
start.setHours(0, 0, 0, 0);
}
return { from: toLocalISOString(start), to: toLocalISOString(end) };
};
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
// Fetch summary and bills in parallel const range = getRangeDates(activeRange);
if (!range) {
setIsLoading(false);
return;
}
const params = new URLSearchParams();
params.append('from', range.from);
params.append('to', range.to);
const queryStr = `?${params.toString()}`;
// Fetch summary and bills in parallel with range params
const [summaryRes, billsRes] = await Promise.all([ const [summaryRes, billsRes] = await Promise.all([
apiFetch('/api/purchases/summary'), apiFetch(`/api/purchases/summary${queryStr}`),
apiFetch('/api/purchases/orders') apiFetch(`/api/purchases/orders${queryStr}`)
]); ]);
if (summaryRes.ok && billsRes.ok) { if (summaryRes.ok && billsRes.ok) {
@@ -46,7 +114,7 @@ const PurchaseSummary = () => {
} }
}; };
fetchData(); fetchData();
}, []); }, [activeRange, customDates]);
if (isLoading || !data) { if (isLoading || !data) {
return ( return (
@@ -84,18 +152,37 @@ const PurchaseSummary = () => {
<h1 className="text-2xl font-black text-slate-800">Summary</h1> <h1 className="text-2xl font-black text-slate-800">Summary</h1>
</div> </div>
<div className="flex items-center gap-2 bg-white p-1 rounded-2xl border border-slate-200 shadow-sm"> <div className="flex flex-col items-end gap-2">
{['Yesterday', 'Today', 'Week', '30 Days'].map(range => ( <div className="flex items-center gap-2 bg-white p-1 rounded-2xl border border-slate-200 shadow-sm">
<button {['Yesterday', 'Today', 'Week', '30 Days', 'Custom'].map(range => (
key={range} <button
onClick={() => setActiveRange(range)} key={range}
className={`px-4 py-2 text-[10px] font-black uppercase tracking-widest transition-all rounded-xl ${activeRange === range ? 'text-white bg-[#003317] shadow-md shadow-[#003317]/20' : 'text-slate-400 hover:text-slate-600'}`} onClick={() => setActiveRange(range)}
> className={`px-4 py-2 text-[10px] font-black uppercase tracking-widest transition-all rounded-xl ${activeRange === range ? 'text-white bg-[#003317] shadow-md shadow-[#003317]/20' : 'text-slate-400 hover:text-slate-600'}`}
{range} >
</button> {range}
))} </button>
</div> ))}
</div> </div>
{activeRange === 'Custom' && (
<div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1 duration-300">
<input
type="date"
value={customDates.from}
onChange={(e) => setCustomDates({ ...customDates, from: e.target.value })}
className="bg-white border border-slate-200 rounded-lg px-2 py-1.5 text-[10px] font-bold text-slate-600 outline-none focus:border-[#003317]"
/>
<span className="text-slate-300 text-[10px] font-bold">to</span>
<input
type="date"
value={customDates.to}
onChange={(e) => setCustomDates({ ...customDates, to: e.target.value })}
className="bg-white border border-slate-200 rounded-lg px-2 py-1.5 text-[10px] font-bold text-slate-600 outline-none focus:border-[#003317]"
/>
</div>
)}
</div>
</div>
<div className="grid grid-cols-12 gap-8"> <div className="grid grid-cols-12 gap-8">
<div className="col-span-12 lg:col-span-5 space-y-6"> <div className="col-span-12 lg:col-span-5 space-y-6">

View File

@@ -1,4 +1,4 @@
import { apiFetch } from '../api'; import { apiFetch } from '../api';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { import {
ChevronRight, ChevronRight,
@@ -27,6 +27,7 @@ import { motion } from 'framer-motion';
const StoreDashboard = () => { const StoreDashboard = () => {
const [activeTab, setActiveTab] = useState('Sales'); const [activeTab, setActiveTab] = useState('Sales');
const [timeRange, setTimeRange] = useState('Today'); const [timeRange, setTimeRange] = useState('Today');
const [customDates, setCustomDates] = useState({ from: '', to: '' });
const [stats, setStats] = useState({ const [stats, setStats] = useState({
totalSales: 0, totalSales: 0,
activeOrders: 0, activeOrders: 0,
@@ -85,6 +86,15 @@ const StoreDashboard = () => {
start.setDate(now.getDate() - 30); start.setDate(now.getDate() - 30);
start.setHours(0, 0, 0, 0); start.setHours(0, 0, 0, 0);
break; break;
case 'Custom':
if (customDates.from && customDates.to) {
const from = new Date(customDates.from);
from.setHours(0, 0, 0, 0);
const to = new Date(customDates.to);
to.setHours(23, 59, 59, 999);
return { from: toLocalISOString(from), to: toLocalISOString(to) };
}
return null;
default: default:
start.setHours(0, 0, 0, 0); start.setHours(0, 0, 0, 0);
} }
@@ -95,12 +105,16 @@ const StoreDashboard = () => {
const fetchStats = async () => { const fetchStats = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
const range = getRangeDates(timeRange); const range = getRangeDates(timeRange);
const params = new URLSearchParams(); if (!range) {
params.append('from', range.from); setIsLoading(false);
params.append('to', range.to); return;
}
const params = new URLSearchParams();
params.append('from', range.from);
params.append('to', range.to);
const response = await apiFetch(`/api/dashboard/stats?${params.toString()}`); const response = await apiFetch(`/api/dashboard/stats?${params.toString()}`);
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
console.log('Dashboard data received successfully:', data); console.log('Dashboard data received successfully:', data);
@@ -138,7 +152,7 @@ const StoreDashboard = () => {
} }
}; };
fetchStats(); fetchStats();
}, [timeRange]); }, [timeRange, customDates]);
const pieData = [ const pieData = [
{ name: 'Full Payment', value: Number(stats.totalSales) || 0, color: '#8b5cf6' }, { name: 'Full Payment', value: Number(stats.totalSales) || 0, color: '#8b5cf6' },
@@ -263,22 +277,41 @@ const StoreDashboard = () => {
{/* Right Stats Column */} {/* Right Stats Column */}
<div className="col-span-12 lg:col-span-3 space-y-6"> <div className="col-span-12 lg:col-span-3 space-y-6">
{/* Filters */} {/* Filters */}
<div className="flex items-center justify-between overflow-x-auto gap-2 pb-2 scrollbar-none"> <div className="flex flex-col gap-3">
{['Yesterday', 'Today', 'Week', '30 Days', 'Custom'].map(range => ( <div className="flex items-center justify-between overflow-x-auto gap-2 pb-2 scrollbar-none">
<button {['Yesterday', 'Today', 'Week', '30 Days', 'Custom'].map(range => (
key={range} <button
title={`Filter metrics by ${range.toLowerCase()}`} key={range}
onClick={() => setTimeRange(range)} title={`Filter metrics by ${range.toLowerCase()}`}
className={`whitespace-nowrap px-2 py-2 text-[10px] font-black uppercase tracking-tighter transition-all relative ${timeRange === range ? 'text-[#003317]' : 'text-slate-400 hover:text-slate-600'}`} onClick={() => setTimeRange(range)}
> className={`whitespace-nowrap px-2 py-2 text-[10px] font-black uppercase tracking-tighter transition-all relative ${timeRange === range ? 'text-[#003317]' : 'text-slate-400 hover:text-slate-600'}`}
{range} >
{timeRange === range && ( {range}
<motion.div layoutId="rangeLineStore" className="absolute bottom-0 left-0 right-0 h-0.5 bg-[#003317]" /> {timeRange === range && (
)} <motion.div layoutId="rangeLineStore" className="absolute bottom-0 left-0 right-0 h-0.5 bg-[#003317]" />
</button> )}
))} </button>
</div> ))}
</div>
{timeRange === 'Custom' && (
<div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1 duration-300">
<input
type="date"
value={customDates.from}
onChange={(e) => setCustomDates({ ...customDates, from: e.target.value })}
className="flex-1 bg-white border border-slate-200 rounded-lg px-2 py-1.5 text-[10px] font-bold text-slate-600 outline-none focus:border-[#003317]"
/>
<span className="text-slate-300 text-[10px] font-bold">to</span>
<input
type="date"
value={customDates.to}
onChange={(e) => setCustomDates({ ...customDates, to: e.target.value })}
className="flex-1 bg-white border border-slate-200 rounded-lg px-2 py-1.5 text-[10px] font-bold text-slate-600 outline-none focus:border-[#003317]"
/>
</div>
)}
</div>
{/* Total Orders Card */} {/* Total Orders Card */}
<div title="View detailed store volume and throughput" className="bg-white p-6 rounded-[24px] border border-slate-100 shadow-sm relative h-[180px] flex flex-col justify-between group cursor-pointer hover:border-[#003317]/30 transition-all"> <div title="View detailed store volume and throughput" className="bg-white p-6 rounded-[24px] border border-slate-100 shadow-sm relative h-[180px] flex flex-col justify-between group cursor-pointer hover:border-[#003317]/30 transition-all">

View File

@@ -41,7 +41,6 @@ const MyOrdersScreen: React.FC = () => {
const [showStockAlert, setShowStockAlert] = useState(false); const [showStockAlert, setShowStockAlert] = useState(false);
const [unavailableItems, setUnavailableItems] = useState<string[]>([]); const [unavailableItems, setUnavailableItems] = useState<string[]>([]);
const [pendingItems, setPendingItems] = useState<FoodItem[]>([]); const [pendingItems, setPendingItems] = useState<FoodItem[]>([]);
const [isRegenerating, setIsRegenerating] = useState(false);
const isOrderExpired = (order: Order) => { const isOrderExpired = (order: Order) => {
if (order.isArchived) return true; if (order.isArchived) return true;
@@ -133,44 +132,6 @@ const MyOrdersScreen: React.FC = () => {
navigate('/cart'); navigate('/cart');
}; };
const handleRegenerateQR = async () => {
if (!selectedOrder) return;
setIsRegenerating(true);
try {
const token = localStorage.getItem('token');
const newOrderNumber = `ORD-${Math.random().toString(36).substring(2, 10).toUpperCase()}`;
const response = await fetch(`http://${window.location.hostname}:8080/api/orders/${selectedOrder.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
},
body: JSON.stringify({
...selectedOrder,
orderNumber: newOrderNumber
})
});
if (response.ok) {
await fetchOrders();
// Since selectedOrder is used in the modal, we need to update it as well
const updated = orders.find(o => o.id === selectedOrder.id);
if (updated) setSelectedOrder({ ...updated, orderNumber: newOrderNumber });
else fetchOrders().then(() => {
// Fallback refresh to catch the update
setOrders(prev => prev.map(o => o.id === selectedOrder.id ? { ...o, orderNumber: newOrderNumber } : o));
setSelectedOrder(prev => prev ? { ...prev, orderNumber: newOrderNumber } : null);
});
} else {
alert('Failed to regenerate sync code');
}
} catch (error) {
console.error('Error regenerating QR:', error);
} finally {
setIsRegenerating(false);
}
};
const handleProceedPartial = () => { const handleProceedPartial = () => {
performRepeat(pendingItems); performRepeat(pendingItems);
setShowStockAlert(false); setShowStockAlert(false);
@@ -324,23 +285,14 @@ const MyOrdersScreen: React.FC = () => {
)} )}
</div> </div>
<p className="modal-qr-hint"> <p className="modal-qr-hint">
{isOrderExpired(selectedOrder) {isOrderExpired(selectedOrder)
? 'QR 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'}
</p> </p>
<button </div>
className={`regenerate-sync-btn ${isRegenerating ? 'loading' : ''}`} <div className="modal-info-list">
onClick={handleRegenerateQR}
disabled={isRegenerating || isOrderExpired(selectedOrder) || selectedOrder.status.toUpperCase() === 'COMPLETED'}
>
<RefreshCcw size={14} className={isRegenerating ? 'animate-spin' : ''} />
{isRegenerating ? 'Generating...' : 'Regenerate Sync ID'}
</button>
</div>
<div className="modal-info-list">
<div className="info-item"> <div className="info-item">
<span className="label">Date</span> <span className="label">Date</span>
<span className="value">{new Date(selectedOrder.createdAt).toLocaleString()}</span> <span className="value">{new Date(selectedOrder.createdAt).toLocaleString()}</span>