import { useState, useEffect } from 'react' import { useParams } from 'react-router-dom' import { ShoppingCart, Plus, Minus, X, Clock, CheckCircle2, ChefHat, Truck, Receipt, ArrowLeft, AlertCircle, Loader2, } from 'lucide-react' import { cn, formatCurrency } from '../lib/utils' import { api } from '../lib/api' import type { RoomServiceMenuItem, RoomServicePublicConfig } from '../lib/api' const BASE = (import.meta.env.VITE_API_URL as string | undefined) ?? 'https://api.hotelsync.ru' type Step = 'menu' | 'cart' | 'payment' | 'status' type OrderStatus = 'new' | 'preparing' | 'ready' | 'delivered' const STATUS_STEPS: { key: OrderStatus; label: string; icon: React.ElementType; desc: string }[] = [ { key: 'new', label: 'Принят', icon: Receipt, desc: 'Заказ отправлен на кухню' }, { key: 'preparing', label: 'Готовится', icon: ChefHat, desc: 'Повар готовит ваш заказ' }, { key: 'ready', label: 'Готов', icon: CheckCircle2, desc: 'Заказ готов, передаём курьеру' }, { key: 'delivered', label: 'Доставлен', icon: Truck, desc: 'Курьер в пути к вашему номеру' }, ] export function GuestRoomServicePage() { const { slug } = useParams<{ slug: string }>() const [config, setConfig] = useState(null) const [menu, setMenu] = useState([]) const [loadErr, setLoadErr] = useState('') const [category, setCategory] = useState('') const [cart, setCart] = useState>({}) const [step, setStep] = useState('menu') const [roomNumber, setRoomNumber] = useState('') const [guestName, setGuestName] = useState('') const [notes, setNotes] = useState('') const [payMethod, setPayMethod] = useState('') const [submitting, setSubmitting] = useState(false) const [submitErr, setSubmitErr] = useState('') const [orderId, setOrderId] = useState('') const [orderStatus, setOrderStatus] = useState('new') // ── Load config + menu ──────────────────────────────────────────────────── useEffect(() => { if (!slug) return Promise.all([ api.roomService.getPublicConfig(slug), api.roomService.getMenu(slug), ]).then(([cfg, items]) => { setConfig(cfg) setMenu(items) if (items.length > 0) setCategory(items[0].category) if (cfg.paymentMethods.length > 0) setPayMethod(cfg.paymentMethods[0].name) }).catch(() => setLoadErr('Не удалось загрузить меню. Попробуйте позже.')) }, [slug]) // ── Poll order status ───────────────────────────────────────────────────── useEffect(() => { if (!orderId || !slug || orderStatus === 'delivered') return const poll = async () => { try { const res = await fetch(`${BASE}/api/hotels/${slug}/room-service/orders/${orderId}/status`) if (res.ok) { const data = await res.json() as { status: string } if (['new','preparing','ready','delivered'].includes(data.status)) { setOrderStatus(data.status as OrderStatus) } } } catch { /* ignore */ } } const id = setInterval(poll, 15000) return () => clearInterval(id) }, [orderId, slug, orderStatus]) // ── Cart logic ──────────────────────────────────────────────────────────── const add = (id: string) => setCart(p => ({ ...p, [id]: (p[id] ?? 0) + 1 })) const sub = (id: string) => setCart(p => ({ ...p, [id]: Math.max(0, (p[id] ?? 0) - 1) })) const cartItems = menu.filter(i => (cart[i.id] ?? 0) > 0) const subtotal = cartItems.reduce((s, i) => s + Number(i.price) * cart[i.id], 0) const svcCharge = Math.round(subtotal * (config?.serviceChargePct ?? 0) / 100) const total = subtotal + svcCharge const cartCount = Object.values(cart).reduce((s, n) => s + n, 0) const canOrder = cartCount > 0 && roomNumber.trim().length > 0 // ── Submit order ────────────────────────────────────────────────────────── const placeOrder = async () => { if (!slug || !canOrder) return setSubmitting(true) setSubmitErr('') try { const order = await api.roomService.createOrder(slug, { roomNumber: roomNumber.trim(), guestName: guestName.trim() || undefined, orderType: 'room', paymentMethod: payMethod || undefined, notes: notes.trim() || undefined, items: cartItems.map(i => ({ menuItemId: i.id, quantity: cart[i.id] })), }) setOrderId(order.id) setOrderStatus('new') setStep('status') } catch { setSubmitErr('Не удалось отправить заказ. Попробуйте ещё раз.') } finally { setSubmitting(false) } } const color = config ? '#2563eb' : '#2563eb' // hotel color if available in future const currentStatusIdx = STATUS_STEPS.findIndex(s => s.key === orderStatus) const categories = [...new Set(menu.map(i => i.category))] // ── Loading ─────────────────────────────────────────────────────────────── if (loadErr) return (

{loadErr}

) if (!config) return (
) return (
{/* Header */}
{step !== 'menu' && step !== 'status' && ( )}

{config.hotelName}

Room Service

{step === 'menu' && cartCount > 0 && ( )}
{/* ── MENU ── */} {step === 'menu' && (
{categories.map(cat => ( ))}
{menu.filter(i => i.category === category && !i.isStopList).map(item => (
{item.emoji}

{item.name}

{item.isPopular && ( Хит )}

{item.prepTimeMin} мин

{formatCurrency(Number(item.price))}

{(cart[item.id] ?? 0) === 0 ? ( ) : (
{cart[item.id]}
)}
))}
{cartCount > 0 && (
)}
)} {/* ── CART ── */} {step === 'cart' && (

Ваш заказ

{cartItems.map((item, i) => (
{item.emoji}

{item.name}

{formatCurrency(Number(item.price))} × {cart[item.id]}

{cart[item.id]}
{formatCurrency(Number(item.price) * cart[item.id])}
))}
Сумма заказа{formatCurrency(subtotal)}
{svcCharge > 0 && (
Обслуживание {config.serviceChargePct}% +{formatCurrency(svcCharge)}
)}
Итого{formatCurrency(total)}
setRoomNumber(e.target.value)} />
setGuestName(e.target.value)} />