import { useState, useEffect, useCallback, useRef } from 'react'
import useAuthStore from '../store/authStore'
import {
getIncomingOrders,
getActiveOrders,
getOrderHistory,
acceptOnlineOrder,
rejectOnlineOrder,
updateOnlineOrderStatus,
} from '../api/client'
// ── Helpers ───────────────────────────────────────────────────────────────────
function timeAgo(iso) {
const diff = Math.floor((Date.now() - new Date(iso).getTime()) / 1000)
if (diff < 60) return `${diff}s ago`
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`
return `${Math.floor(diff / 3600)}h ago`
}
function calcTotals(items = []) {
return items.reduce((sum, it) => sum + (it.unit_price ?? 0) * (it.quantity ?? 1), 0)
}
const STATUS_LABELS = {
pending_acceptance: { label: 'Αναμονή', cls: 'bg-amber-100 text-amber-700' },
accepted: { label: 'Αποδεκτή', cls: 'bg-blue-100 text-blue-700' },
preparing: { label: 'Προετοιμασία', cls: 'bg-indigo-100 text-indigo-700' },
ready: { label: 'Έτοιμη', cls: 'bg-green-100 text-green-700' },
out_for_delivery: { label: 'Παράδοση', cls: 'bg-purple-100 text-purple-700' },
delivered: { label: 'Παραδόθηκε', cls: 'bg-gray-100 text-gray-500' },
rejected: { label: 'Απορρίφθηκε', cls: 'bg-red-100 text-red-600' },
}
function OnlineStatusBadge({ status }) {
const { label, cls } = STATUS_LABELS[status] ?? { label: status, cls: 'bg-gray-100 text-gray-600' }
return (
{label}
)
}
// ── Order Card ────────────────────────────────────────────────────────────────
function OrderCard({ order, onAction }) {
const [rejectOpen, setRejectOpen] = useState(false)
const [rejectReason, setRejectReason] = useState('')
const [busy, setBusy] = useState(false)
const [tick, setTick] = useState(0)
// Refresh the "X min ago" label every 30 s without a full re-render of parent
useEffect(() => {
const id = setInterval(() => setTick(t => t + 1), 30_000)
return () => clearInterval(id)
}, [])
const subtotal = calcTotals(order.items)
const isDelivery = order.online_order_type === 'delivery'
const st = order.online_status
async function act(fn) {
setBusy(true)
try { await fn() } finally { setBusy(false) }
}
return (
{/* Card header */}
{order.online_order_ref ?? `#${order.id}`}
{isDelivery ? 'Delivery' : 'Dine-in'}
{timeAgo(order.opened_at)}
{/* Customer info */}
{order.online_customer_name && (
{order.online_customer_name}
)}
{order.online_customer_phone && (
{order.online_customer_phone}
)}
{isDelivery && order.online_customer_address && (
{order.online_customer_address}
)}
{order.online_customer_notes && (
📝 {order.online_customer_notes}
)}
{/* Items */}
{order.items.map((it, i) => (
{it.quantity} × {it.product?.name ?? `#${it.product_id}`}
€{(it.unit_price * it.quantity).toFixed(2)}
))}
Σύνολο
€{subtotal.toFixed(2)}
{/* Actions */}
{st === 'pending_acceptance' && (
<>
>
)}
{st === 'accepted' && (
)}
{st === 'preparing' && (
isDelivery ? (
) : (
)
)}
{(st === 'ready' || st === 'out_for_delivery') && (
)}
{/* Reject inline form */}
{rejectOpen && (
)}
)
}
// ── Tab Panel ─────────────────────────────────────────────────────────────────
function OrderList({ orders, onAction, emptyText }) {
if (!orders.length) {
return {emptyText}
}
return (
{orders.map(o => (
))}
)
}
// ── Main Page ─────────────────────────────────────────────────────────────────
const TABS = [
{ key: 'incoming', label: 'Εισερχόμενες' },
{ key: 'active', label: 'Ενεργές' },
{ key: 'history', label: 'Ιστορικό' },
]
export default function OnlineOrdersPage() {
const [activeTab, setActiveTab] = useState('incoming')
const [incoming, setIncoming] = useState([])
const [active, setActive] = useState([])
const [history, setHistory] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const token = useAuthStore(s => s.token)
const pollRef = useRef(null)
const fetchAll = useCallback(async () => {
setLoading(true)
setError(null)
try {
const [inc, act, hist] = await Promise.all([
getIncomingOrders(),
getActiveOrders(),
getOrderHistory(),
])
setIncoming(inc.data)
setActive(act.data)
setHistory(hist.data)
} catch {
setError('Σφάλμα φόρτωσης παραγγελιών.')
} finally {
setLoading(false)
}
}, [])
// Initial fetch + 20 s polling
useEffect(() => {
fetchAll()
pollRef.current = setInterval(fetchAll, 20_000)
return () => clearInterval(pollRef.current)
}, [fetchAll])
// SSE listener — refetch on online_order_received or online_order_updated
useEffect(() => {
if (!token) return
const es = new EventSource(`/api/stream?token=${encodeURIComponent(token)}`)
es.onmessage = (e) => {
try {
const { type } = JSON.parse(e.data)
if (type === 'online_order_received' || type === 'online_order_updated') {
fetchAll()
}
} catch { /* ignore malformed frames */ }
}
return () => es.close()
}, [token, fetchAll])
async function handleAction(action, orderId, extra) {
try {
if (action === 'accept') {
const { data: updated } = await acceptOnlineOrder(orderId)
setIncoming(prev => prev.filter(o => o.id !== orderId))
setActive(prev => [updated, ...prev])
} else if (action === 'reject') {
const { data: updated } = await rejectOnlineOrder(orderId, extra)
setIncoming(prev => prev.filter(o => o.id !== orderId))
setHistory(prev => [updated, ...prev])
} else if (action === 'status') {
const { data: updated } = await updateOnlineOrderStatus(orderId, extra)
const isDone = updated.online_status === 'delivered' || updated.online_status === 'rejected'
if (isDone) {
setActive(prev => prev.filter(o => o.id !== orderId))
setHistory(prev => [updated, ...prev])
} else {
setActive(prev => prev.map(o => o.id === orderId ? updated : o))
}
}
} catch {
// Re-fetch to sync — avoids stale state after a failed optimistic update
fetchAll()
}
}
const pendingCount = incoming.length
return (
{/* Page header */}
Online Παραγγελίες
{pendingCount > 0 && (
{pendingCount}
)}
{/* Tabs */}
{TABS.map(tab => {
const count = tab.key === 'incoming' ? incoming.length
: tab.key === 'active' ? active.length
: history.length
const isActive = activeTab === tab.key
return (
)
})}
{/* Content */}
{error && (
{error}
)}
{activeTab === 'incoming' && (
)}
{activeTab === 'active' && (
)}
{activeTab === 'history' && (
)}
)
}